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  }