github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/cache/cache.go (about)

     1  // Package cache implements the Fs cache
     2  package cache
     3  
     4  import (
     5  	"context"
     6  	"runtime"
     7  	"sync"
     8  
     9  	"github.com/rclone/rclone/fs"
    10  	"github.com/rclone/rclone/fs/filter"
    11  	"github.com/rclone/rclone/lib/cache"
    12  )
    13  
    14  var (
    15  	once  sync.Once // creation
    16  	c     *cache.Cache
    17  	mu    sync.Mutex            // mutex to protect remap
    18  	remap = map[string]string{} // map user supplied names to canonical names
    19  )
    20  
    21  // Create the cache just once
    22  func createOnFirstUse() {
    23  	once.Do(func() {
    24  		ci := fs.GetConfig(context.Background())
    25  		c = cache.New()
    26  		c.SetExpireDuration(ci.FsCacheExpireDuration)
    27  		c.SetExpireInterval(ci.FsCacheExpireInterval)
    28  		c.SetFinalizer(func(value interface{}) {
    29  			if s, ok := value.(fs.Shutdowner); ok {
    30  				_ = fs.CountError(s.Shutdown(context.Background()))
    31  			}
    32  		})
    33  	})
    34  }
    35  
    36  // Canonicalize looks up fsString in the mapping from user supplied
    37  // names to canonical names and return the canonical form
    38  func Canonicalize(fsString string) string {
    39  	createOnFirstUse()
    40  	mu.Lock()
    41  	canonicalName, ok := remap[fsString]
    42  	mu.Unlock()
    43  	if !ok {
    44  		return fsString
    45  	}
    46  	fs.Debugf(nil, "fs cache: switching user supplied name %q for canonical name %q", fsString, canonicalName)
    47  	return canonicalName
    48  }
    49  
    50  // Put in a mapping from fsString => canonicalName if they are different
    51  func addMapping(fsString, canonicalName string) {
    52  	if canonicalName == fsString {
    53  		return
    54  	}
    55  	mu.Lock()
    56  	remap[fsString] = canonicalName
    57  	mu.Unlock()
    58  }
    59  
    60  // GetFn gets an fs.Fs named fsString either from the cache or creates
    61  // it afresh with the create function
    62  func GetFn(ctx context.Context, fsString string, create func(ctx context.Context, fsString string) (fs.Fs, error)) (f fs.Fs, err error) {
    63  	createOnFirstUse()
    64  	canonicalFsString := Canonicalize(fsString)
    65  	created := false
    66  	value, err := c.Get(canonicalFsString, func(canonicalFsString string) (f interface{}, ok bool, err error) {
    67  		f, err = create(ctx, fsString) // always create the backend with the original non-canonicalised string
    68  		ok = err == nil || err == fs.ErrorIsFile
    69  		created = ok
    70  		return f, ok, err
    71  	})
    72  	if err != nil && err != fs.ErrorIsFile {
    73  		return nil, err
    74  	}
    75  	f = value.(fs.Fs)
    76  	// Check we stored the Fs at the canonical name
    77  	if created {
    78  		canonicalName := fs.ConfigString(f)
    79  		if canonicalName != canonicalFsString {
    80  			// Note that if err == fs.ErrorIsFile at this moment
    81  			// then we can't rename the remote as it will have the
    82  			// wrong error status, we need to add a new one.
    83  			if err == nil {
    84  				fs.Debugf(nil, "fs cache: renaming cache item %q to be canonical %q", canonicalFsString, canonicalName)
    85  				value, found := c.Rename(canonicalFsString, canonicalName)
    86  				if found {
    87  					f = value.(fs.Fs)
    88  				}
    89  				addMapping(canonicalFsString, canonicalName)
    90  			} else {
    91  				fs.Debugf(nil, "fs cache: adding new entry for parent of %q, %q", canonicalFsString, canonicalName)
    92  				Put(canonicalName, f)
    93  			}
    94  		}
    95  	}
    96  	return f, err
    97  }
    98  
    99  // Pin f into the cache until Unpin is called
   100  func Pin(f fs.Fs) {
   101  	createOnFirstUse()
   102  	c.Pin(fs.ConfigString(f))
   103  }
   104  
   105  // PinUntilFinalized pins f into the cache until x is garbage collected
   106  //
   107  // This calls runtime.SetFinalizer on x so it shouldn't have a
   108  // finalizer already.
   109  func PinUntilFinalized(f fs.Fs, x interface{}) {
   110  	Pin(f)
   111  	runtime.SetFinalizer(x, func(_ interface{}) {
   112  		Unpin(f)
   113  	})
   114  
   115  }
   116  
   117  // Unpin f from the cache
   118  func Unpin(f fs.Fs) {
   119  	createOnFirstUse()
   120  	c.Unpin(fs.ConfigString(f))
   121  }
   122  
   123  // To avoid circular dependencies these are filled in by fs/rc/jobs/job.go
   124  var (
   125  	// JobGetJobID for internal use only
   126  	JobGetJobID func(context.Context) (int64, bool)
   127  	// JobOnFinish for internal use only
   128  	JobOnFinish func(int64, func()) (func(), error)
   129  )
   130  
   131  // Get gets an fs.Fs named fsString either from the cache or creates it afresh
   132  func Get(ctx context.Context, fsString string) (f fs.Fs, err error) {
   133  	// If we are making a long lived backend which lives longer
   134  	// than this request, we want to disconnect it from the
   135  	// current context and in particular any WithCancel contexts,
   136  	// but we want to preserve the config embedded in the context.
   137  	newCtx := context.Background()
   138  	newCtx = fs.CopyConfig(newCtx, ctx)
   139  	newCtx = filter.CopyConfig(newCtx, ctx)
   140  	f, err = GetFn(newCtx, fsString, fs.NewFs)
   141  	if f == nil || (err != nil && err != fs.ErrorIsFile) {
   142  		return f, err
   143  	}
   144  	// If this is part of an rc job then pin the backend until it finishes
   145  	if JobOnFinish != nil && JobGetJobID != nil {
   146  		if jobID, ok := JobGetJobID(ctx); ok {
   147  			// fs.Debugf(f, "Pin for job %d", jobID)
   148  			Pin(f)
   149  			_, _ = JobOnFinish(jobID, func() {
   150  				// fs.Debugf(f, "Unpin for job %d", jobID)
   151  				Unpin(f)
   152  			})
   153  		}
   154  	}
   155  	return f, err
   156  }
   157  
   158  // GetArr gets []fs.Fs from []fsStrings either from the cache or creates it afresh
   159  func GetArr(ctx context.Context, fsStrings []string) (f []fs.Fs, err error) {
   160  	var fArr []fs.Fs
   161  	for _, fsString := range fsStrings {
   162  		f1, err1 := GetFn(ctx, fsString, fs.NewFs)
   163  		if err1 != nil {
   164  			return fArr, err1
   165  		}
   166  		fArr = append(fArr, f1)
   167  	}
   168  	return fArr, nil
   169  }
   170  
   171  // PutErr puts an fs.Fs named fsString into the cache with err
   172  func PutErr(fsString string, f fs.Fs, err error) {
   173  	createOnFirstUse()
   174  	canonicalName := fs.ConfigString(f)
   175  	c.PutErr(canonicalName, f, err)
   176  	addMapping(fsString, canonicalName)
   177  }
   178  
   179  // Put puts an fs.Fs named fsString into the cache
   180  func Put(fsString string, f fs.Fs) {
   181  	PutErr(fsString, f, nil)
   182  }
   183  
   184  // ClearConfig deletes all entries which were based on the config name passed in
   185  //
   186  // Returns number of entries deleted
   187  func ClearConfig(name string) (deleted int) {
   188  	createOnFirstUse()
   189  	return c.DeletePrefix(name + ":")
   190  }
   191  
   192  // Clear removes everything from the cache
   193  func Clear() {
   194  	createOnFirstUse()
   195  	c.Clear()
   196  }
   197  
   198  // Entries returns the number of entries in the cache
   199  func Entries() int {
   200  	createOnFirstUse()
   201  	return c.Entries()
   202  }