github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/dirent_cache.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fs 16 17 import ( 18 "fmt" 19 20 "github.com/SagerNet/gvisor/pkg/context" 21 "github.com/SagerNet/gvisor/pkg/sync" 22 ) 23 24 // DirentCache is an LRU cache of Dirents. The Dirent's refCount is 25 // incremented when it is added to the cache, and decremented when it is 26 // removed. 27 // 28 // A nil DirentCache corresponds to a cache with size 0. All methods can be 29 // called, but nothing is actually cached. 30 // 31 // +stateify savable 32 type DirentCache struct { 33 // Maximum size of the cache. This must be saved manually, to handle the case 34 // when cache is nil. 35 maxSize uint64 36 37 // limit restricts the number of entries in the cache amoung multiple caches. 38 // It may be nil if there are no global limit for this cache. 39 limit *DirentCacheLimiter 40 41 // mu protects currentSize and direntList. 42 mu sync.Mutex `state:"nosave"` 43 44 // currentSize is the number of elements in the cache. It must be zero (i.e. 45 // the cache must be empty) on Save. 46 currentSize uint64 `state:"zerovalue"` 47 48 // list is a direntList, an ilist of Dirents. New Dirents are added 49 // to the front of the list. Old Dirents are removed from the back of 50 // the list. It must be zerovalue (i.e. the cache must be empty) on Save. 51 list direntList `state:"zerovalue"` 52 } 53 54 // NewDirentCache returns a new DirentCache with the given maxSize. 55 func NewDirentCache(maxSize uint64) *DirentCache { 56 return &DirentCache{ 57 maxSize: maxSize, 58 } 59 } 60 61 // Add adds the element to the cache and increments the refCount. If the 62 // argument is already in the cache, it is moved to the front. An element is 63 // removed from the back if the cache is over capacity. 64 func (c *DirentCache) Add(d *Dirent) { 65 if c == nil || c.maxSize == 0 { 66 return 67 } 68 69 c.mu.Lock() 70 if c.contains(d) { 71 // d is already in cache. Bump it to the front. 72 // currentSize and refCount are unaffected. 73 c.list.Remove(d) 74 c.list.PushFront(d) 75 c.mu.Unlock() 76 return 77 } 78 79 // First check against the global limit. 80 for c.limit != nil && !c.limit.tryInc() { 81 if c.currentSize == 0 { 82 // If the global limit is reached, but there is nothing more to drop from 83 // this cache, there is not much else to do. 84 c.mu.Unlock() 85 return 86 } 87 c.remove(c.list.Back()) 88 } 89 90 // d is not in cache. Add it and take a reference. 91 c.list.PushFront(d) 92 d.IncRef() 93 c.currentSize++ 94 95 c.maybeShrink() 96 97 c.mu.Unlock() 98 } 99 100 func (c *DirentCache) remove(d *Dirent) { 101 if !c.contains(d) { 102 panic(fmt.Sprintf("trying to remove %v, which is not in the dirent cache", d)) 103 } 104 c.list.Remove(d) 105 d.DecRef(context.Background()) 106 c.currentSize-- 107 if c.limit != nil { 108 c.limit.dec() 109 } 110 } 111 112 // Remove removes the element from the cache and decrements its refCount. It 113 // also sets the previous and next elements to nil, which allows us to 114 // determine if a given element is in the cache. 115 func (c *DirentCache) Remove(d *Dirent) { 116 if c == nil || c.maxSize == 0 { 117 return 118 } 119 c.mu.Lock() 120 if !c.contains(d) { 121 c.mu.Unlock() 122 return 123 } 124 c.remove(d) 125 c.mu.Unlock() 126 } 127 128 // Size returns the number of elements in the cache. 129 func (c *DirentCache) Size() uint64 { 130 if c == nil { 131 return 0 132 } 133 c.mu.Lock() 134 size := c.currentSize 135 c.mu.Unlock() 136 return size 137 } 138 139 func (c *DirentCache) contains(d *Dirent) bool { 140 // If d has a Prev or Next element, then it is in the cache. 141 if d.Prev() != nil || d.Next() != nil { 142 return true 143 } 144 // Otherwise, d is in the cache if it is the only element (and thus the 145 // first element). 146 return c.list.Front() == d 147 } 148 149 // Invalidate removes all Dirents from the cache, calling DecRef on each. 150 func (c *DirentCache) Invalidate() { 151 if c == nil { 152 return 153 } 154 c.mu.Lock() 155 for c.list.Front() != nil { 156 c.remove(c.list.Front()) 157 } 158 c.mu.Unlock() 159 } 160 161 // setMaxSize sets cache max size. If current size is larger than max size, the 162 // cache shrinks to accommodate the new max. 163 func (c *DirentCache) setMaxSize(max uint64) { 164 c.mu.Lock() 165 c.maxSize = max 166 c.maybeShrink() 167 c.mu.Unlock() 168 } 169 170 // shrink removes the oldest element until the list is under the size limit. 171 func (c *DirentCache) maybeShrink() { 172 for c.maxSize > 0 && c.currentSize > c.maxSize { 173 c.remove(c.list.Back()) 174 } 175 }