github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/vfscache/cache.go (about) 1 // Package vfscache deals with caching of files locally for the VFS layer 2 package vfscache 3 4 import ( 5 "context" 6 "errors" 7 "fmt" 8 "os" 9 "path" 10 "path/filepath" 11 "runtime" 12 "sort" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/rclone/rclone/fs" 18 fscache "github.com/rclone/rclone/fs/cache" 19 "github.com/rclone/rclone/fs/config" 20 "github.com/rclone/rclone/fs/fserrors" 21 "github.com/rclone/rclone/fs/hash" 22 "github.com/rclone/rclone/fs/operations" 23 "github.com/rclone/rclone/fs/rc" 24 "github.com/rclone/rclone/lib/diskusage" 25 "github.com/rclone/rclone/lib/encoder" 26 "github.com/rclone/rclone/lib/file" 27 "github.com/rclone/rclone/lib/systemd" 28 "github.com/rclone/rclone/vfs/vfscache/writeback" 29 "github.com/rclone/rclone/vfs/vfscommon" 30 ) 31 32 // NB as Cache and Item are tightly linked it is necessary to have a 33 // total lock ordering between them. So Cache.mu must always be 34 // taken before Item.mu to avoid deadlocks. 35 // 36 // Cache may call into Item but care is needed if Item calls Cache 37 38 // FIXME need to purge cache nodes which don't have backing files and aren't dirty 39 // these may get created by the VFS layer or may be orphans from reload() 40 41 // Cache opened files 42 type Cache struct { 43 // read only - no locking needed to read these 44 fremote fs.Fs // fs for the remote we are caching 45 fcache fs.Fs // fs for the cache directory 46 fcacheMeta fs.Fs // fs for the cache metadata directory 47 opt *vfscommon.Options // vfs Options 48 root string // root of the cache directory 49 metaRoot string // root of the cache metadata directory 50 hashType hash.Type // hash to use locally and remotely 51 hashOption *fs.HashesOption // corresponding OpenOption 52 writeback *writeback.WriteBack // holds Items for writeback 53 avFn AddVirtualFn // if set, can be called to add dir entries 54 55 mu sync.Mutex // protects the following variables 56 cond sync.Cond // cond lock for synchronous cache cleaning 57 item map[string]*Item // files/directories in the cache 58 errItems map[string]error // items in error state 59 used int64 // total size of files in the cache 60 outOfSpace bool // out of space 61 cleanerKicked bool // some thread kicked the cleaner upon out of space 62 kickerMu sync.Mutex // mutex for cleanerKicked 63 kick chan struct{} // channel for kicking clear to start 64 65 } 66 67 // AddVirtualFn if registered by the WithAddVirtual method, can be 68 // called to register the object or directory at remote as a virtual 69 // entry in directory listings. 70 // 71 // This is used when reloading the Cache and uploading items need to 72 // go into the directory tree. 73 type AddVirtualFn func(remote string, size int64, isDir bool) error 74 75 // New creates a new cache hierarchy for fremote 76 // 77 // This starts background goroutines which can be cancelled with the 78 // context passed in. 79 func New(ctx context.Context, fremote fs.Fs, opt *vfscommon.Options, avFn AddVirtualFn) (*Cache, error) { 80 // Get cache root path. 81 // We need it in two variants: OS path as an absolute path with UNC prefix, 82 // OS-specific path separators, and encoded with OS-specific encoder. Standard path 83 // without UNC prefix, with slash path separators, and standard (internal) encoding. 84 // Care must be taken when creating OS paths so that the ':' separator following a 85 // drive letter is not encoded (e.g. into unicode fullwidth colon). 86 var err error 87 parentOSPath := config.GetCacheDir() // Assuming string contains a local absolute path in OS encoding 88 fs.Debugf(nil, "vfs cache: root is %q", parentOSPath) 89 parentPath := fromOSPath(parentOSPath) 90 91 // Get a relative cache path representing the remote. 92 relativeDirPath := fremote.Root() // This is a remote path in standard encoding 93 if runtime.GOOS == "windows" { 94 if strings.HasPrefix(relativeDirPath, `//?/`) { 95 relativeDirPath = relativeDirPath[2:] // Trim off the "//" for the result to be a valid when appending to another path 96 } 97 } 98 relativeDirPath = fremote.Name() + "/" + relativeDirPath 99 relativeDirOSPath := toOSPath(relativeDirPath) 100 101 // Create cache root dirs 102 var dataOSPath, metaOSPath string 103 if dataOSPath, metaOSPath, err = createRootDirs(parentOSPath, relativeDirOSPath); err != nil { 104 return nil, err 105 } 106 fs.Debugf(nil, "vfs cache: data root is %q", dataOSPath) 107 fs.Debugf(nil, "vfs cache: metadata root is %q", metaOSPath) 108 109 // Get (create) cache backends 110 var fdata, fmeta fs.Fs 111 if fdata, fmeta, err = getBackends(ctx, parentPath, relativeDirPath); err != nil { 112 return nil, err 113 } 114 hashType, hashOption := operations.CommonHash(ctx, fdata, fremote) 115 116 // Create the cache object 117 c := &Cache{ 118 fremote: fremote, 119 fcache: fdata, 120 fcacheMeta: fmeta, 121 opt: opt, 122 root: dataOSPath, 123 metaRoot: metaOSPath, 124 item: make(map[string]*Item), 125 errItems: make(map[string]error), 126 hashType: hashType, 127 hashOption: hashOption, 128 writeback: writeback.New(ctx, opt), 129 avFn: avFn, 130 } 131 132 // load in the cache and metadata off disk 133 err = c.reload(ctx) 134 if err != nil { 135 return nil, fmt.Errorf("failed to load cache: %w", err) 136 } 137 138 // Remove any empty directories 139 c.purgeEmptyDirs("", true) 140 141 // Create a channel for cleaner to be kicked upon out of space con 142 c.kick = make(chan struct{}, 1) 143 c.cond = sync.Cond{L: &c.mu} 144 145 go c.cleaner(ctx) 146 147 return c, nil 148 } 149 150 // Stats returns info about the Cache 151 func (c *Cache) Stats() (out rc.Params) { 152 out = make(rc.Params) 153 // read only - no locking needed to read these 154 out["path"] = c.root 155 out["pathMeta"] = c.metaRoot 156 out["hashType"] = c.hashType 157 158 uploadsInProgress, uploadsQueued := c.writeback.Stats() 159 out["uploadsInProgress"] = uploadsInProgress 160 out["uploadsQueued"] = uploadsQueued 161 162 c.mu.Lock() 163 defer c.mu.Unlock() 164 165 out["files"] = len(c.item) 166 out["erroredFiles"] = len(c.errItems) 167 out["bytesUsed"] = c.used 168 out["outOfSpace"] = c.outOfSpace 169 170 return out 171 } 172 173 // createDir creates a directory path, along with any necessary parents 174 func createDir(dir string) error { 175 return file.MkdirAll(dir, 0700) 176 } 177 178 // createRootDir creates a single cache root directory 179 func createRootDir(parentOSPath string, name string, relativeDirOSPath string) (path string, err error) { 180 path = file.UNCPath(filepath.Join(parentOSPath, name, relativeDirOSPath)) 181 err = createDir(path) 182 return 183 } 184 185 // createRootDirs creates all cache root directories 186 func createRootDirs(parentOSPath string, relativeDirOSPath string) (dataOSPath string, metaOSPath string, err error) { 187 if dataOSPath, err = createRootDir(parentOSPath, "vfs", relativeDirOSPath); err != nil { 188 err = fmt.Errorf("failed to create data cache directory: %w", err) 189 } else if metaOSPath, err = createRootDir(parentOSPath, "vfsMeta", relativeDirOSPath); err != nil { 190 err = fmt.Errorf("failed to create metadata cache directory: %w", err) 191 } 192 return 193 } 194 195 // createItemDir creates the directory for named item in all cache roots 196 // 197 // Returns an os path for the data cache file. 198 func (c *Cache) createItemDir(name string) (string, error) { 199 parent := vfscommon.FindParent(name) 200 parentPath := c.toOSPath(parent) 201 err := createDir(parentPath) 202 if err != nil { 203 return "", fmt.Errorf("failed to create data cache item directory: %w", err) 204 } 205 parentPathMeta := c.toOSPathMeta(parent) 206 err = createDir(parentPathMeta) 207 if err != nil { 208 return "", fmt.Errorf("failed to create metadata cache item directory: %w", err) 209 } 210 return c.toOSPath(name), nil 211 } 212 213 // getBackend gets a backend for a cache root dir 214 func getBackend(ctx context.Context, parentPath string, name string, relativeDirPath string) (fs.Fs, error) { 215 path := fmt.Sprintf("%s/%s/%s", parentPath, name, relativeDirPath) 216 return fscache.Get(ctx, path) 217 } 218 219 // getBackends gets backends for all cache root dirs 220 func getBackends(ctx context.Context, parentPath string, relativeDirPath string) (fdata fs.Fs, fmeta fs.Fs, err error) { 221 if fdata, err = getBackend(ctx, parentPath, "vfs", relativeDirPath); err != nil { 222 err = fmt.Errorf("failed to get data cache backend: %w", err) 223 } else if fmeta, err = getBackend(ctx, parentPath, "vfsMeta", relativeDirPath); err != nil { 224 err = fmt.Errorf("failed to get metadata cache backend: %w", err) 225 } 226 return 227 } 228 229 // clean returns the cleaned version of name for use in the index map 230 // 231 // name should be a remote path not an osPath 232 func clean(name string) string { 233 name = strings.Trim(name, "/") 234 name = path.Clean(name) 235 if name == "." || name == "/" { 236 name = "" 237 } 238 return name 239 } 240 241 // fromOSPath turns a OS path into a standard/remote path 242 func fromOSPath(osPath string) string { 243 return encoder.OS.ToStandardPath(filepath.ToSlash(osPath)) 244 } 245 246 // toOSPath turns a standard/remote path into an OS path 247 func toOSPath(standardPath string) string { 248 return filepath.FromSlash(encoder.OS.FromStandardPath(standardPath)) 249 } 250 251 // toOSPath turns a remote relative name into an OS path in the cache 252 func (c *Cache) toOSPath(name string) string { 253 return filepath.Join(c.root, toOSPath(name)) 254 } 255 256 // toOSPathMeta turns a remote relative name into an OS path in the 257 // cache for the metadata 258 func (c *Cache) toOSPathMeta(name string) string { 259 return filepath.Join(c.metaRoot, toOSPath(name)) 260 } 261 262 // _get gets name from the cache or creates a new one 263 // 264 // It returns the item and found as to whether this item was found in 265 // the cache (or just created). 266 // 267 // name should be a remote path not an osPath 268 // 269 // must be called with mu held 270 func (c *Cache) _get(name string) (item *Item, found bool) { 271 item = c.item[name] 272 found = item != nil 273 if !found { 274 item = newItem(c, name) 275 c.item[name] = item 276 } 277 return item, found 278 } 279 280 // put puts item under name in the cache 281 // 282 // It returns an old item if there was one or nil if not. 283 // 284 // name should be a remote path not an osPath 285 func (c *Cache) put(name string, item *Item) (oldItem *Item) { 286 name = clean(name) 287 c.mu.Lock() 288 oldItem = c.item[name] 289 if oldItem != item { 290 c.item[name] = item 291 } else { 292 oldItem = nil 293 } 294 c.mu.Unlock() 295 return oldItem 296 } 297 298 // InUse returns whether the name is in use in the cache 299 // 300 // name should be a remote path not an osPath 301 func (c *Cache) InUse(name string) bool { 302 name = clean(name) 303 c.mu.Lock() 304 item := c.item[name] 305 c.mu.Unlock() 306 if item == nil { 307 return false 308 } 309 return item.inUse() 310 } 311 312 // DirtyItem returns the Item if it exists in the cache **and** is 313 // dirty otherwise it returns nil. 314 // 315 // name should be a remote path not an osPath 316 func (c *Cache) DirtyItem(name string) (item *Item) { 317 name = clean(name) 318 c.mu.Lock() 319 defer c.mu.Unlock() 320 item = c.item[name] 321 if item != nil && !item.IsDirty() { 322 item = nil 323 } 324 return item 325 } 326 327 // get gets a file name from the cache or creates a new one 328 // 329 // It returns the item and found as to whether this item was found in 330 // the cache (or just created). 331 // 332 // name should be a remote path not an osPath 333 func (c *Cache) get(name string) (item *Item, found bool) { 334 name = clean(name) 335 c.mu.Lock() 336 item, found = c._get(name) 337 c.mu.Unlock() 338 return item, found 339 } 340 341 // Item gets a cache item for name 342 // 343 // To use it item.Open will need to be called. 344 // 345 // name should be a remote path not an osPath 346 func (c *Cache) Item(name string) (item *Item) { 347 item, _ = c.get(name) 348 return item 349 } 350 351 // Exists checks to see if the file exists in the cache or not. 352 // 353 // This is done by bringing the item into the cache which will 354 // validate the backing file and metadata and then asking if the Item 355 // exists or not. 356 func (c *Cache) Exists(name string) bool { 357 item, _ := c.get(name) 358 return item.Exists() 359 } 360 361 // rename with os.Rename and more checking 362 func rename(osOldPath, osNewPath string) error { 363 sfi, err := os.Stat(osOldPath) 364 if err != nil { 365 // Just do nothing if the source does not exist 366 if os.IsNotExist(err) { 367 return nil 368 } 369 return fmt.Errorf("failed to stat source: %s: %w", osOldPath, err) 370 } 371 if !sfi.Mode().IsRegular() { 372 // cannot copy non-regular files (e.g., directories, symlinks, devices, etc.) 373 return fmt.Errorf("non-regular source file: %s (%q)", sfi.Name(), sfi.Mode().String()) 374 } 375 dfi, err := os.Stat(osNewPath) 376 if err != nil { 377 if !os.IsNotExist(err) { 378 return fmt.Errorf("failed to stat destination: %s: %w", osNewPath, err) 379 } 380 parent := vfscommon.OSFindParent(osNewPath) 381 err = createDir(parent) 382 if err != nil { 383 return fmt.Errorf("failed to create parent dir: %s: %w", parent, err) 384 } 385 } else { 386 if !(dfi.Mode().IsRegular()) { 387 return fmt.Errorf("non-regular destination file: %s (%q)", dfi.Name(), dfi.Mode().String()) 388 } 389 if os.SameFile(sfi, dfi) { 390 return nil 391 } 392 } 393 if err = os.Rename(osOldPath, osNewPath); err != nil { 394 return fmt.Errorf("failed to rename in cache: %s to %s: %w", osOldPath, osNewPath, err) 395 } 396 return nil 397 } 398 399 // Rename the item in cache 400 func (c *Cache) Rename(name string, newName string, newObj fs.Object) (err error) { 401 item, _ := c.get(name) 402 err = item.rename(name, newName, newObj) 403 if err != nil { 404 return err 405 } 406 407 // Move the item in the cache 408 c.mu.Lock() 409 if item, ok := c.item[name]; ok { 410 c.item[newName] = item 411 delete(c.item, name) 412 } 413 c.mu.Unlock() 414 415 fs.Infof(name, "vfs cache: renamed in cache to %q", newName) 416 return nil 417 } 418 419 // DirExists checks to see if the directory exists in the cache or not. 420 func (c *Cache) DirExists(name string) bool { 421 path := c.toOSPath(name) 422 _, err := os.Stat(path) 423 return err == nil 424 } 425 426 // DirRename the dir in cache 427 func (c *Cache) DirRename(oldDirName string, newDirName string) (err error) { 428 // Make sure names are / suffixed for reading keys out of c.item 429 if !strings.HasSuffix(oldDirName, "/") { 430 oldDirName += "/" 431 } 432 if !strings.HasSuffix(newDirName, "/") { 433 newDirName += "/" 434 } 435 436 // Find all items to rename 437 var renames []string 438 c.mu.Lock() 439 for itemName := range c.item { 440 if strings.HasPrefix(itemName, oldDirName) { 441 renames = append(renames, itemName) 442 } 443 } 444 c.mu.Unlock() 445 446 // Rename the items 447 for _, itemName := range renames { 448 newPath := newDirName + itemName[len(oldDirName):] 449 renameErr := c.Rename(itemName, newPath, nil) 450 if renameErr != nil { 451 err = renameErr 452 } 453 } 454 455 // Old path should be empty now so remove it 456 c.purgeEmptyDirs(oldDirName[:len(oldDirName)-1], false) 457 458 fs.Infof(oldDirName, "vfs cache: renamed dir in cache to %q", newDirName) 459 return err 460 } 461 462 // Remove should be called if name is deleted 463 // 464 // This returns true if the file was in the transfer queue so may not 465 // have completely uploaded yet. 466 func (c *Cache) Remove(name string) (wasWriting bool) { 467 name = clean(name) 468 c.mu.Lock() 469 item := c.item[name] 470 if item != nil { 471 delete(c.item, name) 472 } 473 c.mu.Unlock() 474 if item == nil { 475 return false 476 } 477 return item.remove("file deleted") 478 } 479 480 // SetModTime should be called to set the modification time of the cache file 481 func (c *Cache) SetModTime(name string, modTime time.Time) { 482 item, _ := c.get(name) 483 item.setModTime(modTime) 484 } 485 486 // CleanUp empties the cache of everything 487 func (c *Cache) CleanUp() error { 488 err1 := os.RemoveAll(c.root) 489 err2 := os.RemoveAll(c.metaRoot) 490 if err1 != nil { 491 return err1 492 } 493 return err2 494 } 495 496 // walk walks the cache calling the function 497 func (c *Cache) walk(dir string, fn func(osPath string, fi os.FileInfo, name string) error) error { 498 return filepath.Walk(dir, func(osPath string, fi os.FileInfo, err error) error { 499 if err != nil { 500 return err 501 } 502 // Find path relative to the cache root 503 name, err := filepath.Rel(dir, osPath) 504 if err != nil { 505 return fmt.Errorf("filepath.Rel failed in walk: %w", err) 506 } 507 if name == "." { 508 name = "" 509 } 510 // And convert into slashes 511 name = filepath.ToSlash(name) 512 513 return fn(osPath, fi, name) 514 }) 515 } 516 517 // reload walks the cache loading metadata files 518 // 519 // It iterates the files first then metadata trees. It doesn't expect 520 // to find any new items iterating the metadata but it will clear up 521 // orphan files. 522 func (c *Cache) reload(ctx context.Context) error { 523 for _, dir := range []string{c.root, c.metaRoot} { 524 err := c.walk(dir, func(osPath string, fi os.FileInfo, name string) error { 525 if fi.IsDir() { 526 return nil 527 } 528 item, found := c.get(name) 529 if !found { 530 err := item.reload(ctx) 531 if err != nil { 532 fs.Errorf(name, "vfs cache: failed to reload item: %v", err) 533 } 534 } 535 return nil 536 }) 537 if err != nil { 538 return fmt.Errorf("failed to walk cache %q: %w", dir, err) 539 } 540 } 541 return nil 542 } 543 544 // KickCleaner kicks cache cleaner upon out of space situation 545 func (c *Cache) KickCleaner() { 546 /* Use a separate kicker mutex for the kick to go through without waiting for the 547 cache mutex to avoid letting a thread kick again after the clearer just 548 finished cleaning and unlock the cache mutex. */ 549 fs.Debugf(nil, "vfs cache: at the beginning of KickCleaner") 550 c.kickerMu.Lock() 551 if !c.cleanerKicked { 552 c.cleanerKicked = true 553 fs.Debugf(nil, "vfs cache: in KickCleaner, ready to lock cache mutex") 554 c.mu.Lock() 555 c.outOfSpace = true 556 fs.Logf(nil, "vfs cache: in KickCleaner, ready to kick cleaner") 557 c.kick <- struct{}{} 558 c.mu.Unlock() 559 } 560 c.kickerMu.Unlock() 561 562 c.mu.Lock() 563 for c.outOfSpace { 564 fs.Debugf(nil, "vfs cache: in KickCleaner, looping on c.outOfSpace") 565 c.cond.Wait() 566 } 567 fs.Debugf(nil, "vfs cache: in KickCleaner, leaving c.outOfSpace loop") 568 c.mu.Unlock() 569 } 570 571 // removeNotInUse removes items not in use with a possible maxAge cutoff 572 // called with cache mutex locked and up-to-date c.used (as we update it directly here) 573 func (c *Cache) removeNotInUse(item *Item, maxAge time.Duration, emptyOnly bool) { 574 removed, spaceFreed := item.RemoveNotInUse(maxAge, emptyOnly) 575 // The item space might be freed even if we get an error after the cache file is removed 576 // The item will not be removed or reset the cache data is dirty (DataDirty) 577 c.used -= spaceFreed 578 if removed { 579 fs.Infof(nil, "vfs cache RemoveNotInUse (maxAge=%d, emptyOnly=%v): item %s was removed, freed %d bytes", maxAge, emptyOnly, item.GetName(), spaceFreed) 580 // Remove the entry 581 delete(c.item, item.name) 582 } else { 583 fs.Debugf(nil, "vfs cache RemoveNotInUse (maxAge=%d, emptyOnly=%v): item %s not removed, freed %d bytes", maxAge, emptyOnly, item.GetName(), spaceFreed) 584 } 585 } 586 587 // Retry failed resets during purgeClean() 588 func (c *Cache) retryFailedResets() { 589 // Some items may have failed to reset because there was not enough space 590 // for saving the cache item's metadata. Redo the Reset()'s here now that 591 // we may have some available space. 592 if len(c.errItems) != 0 { 593 fs.Debugf(nil, "vfs cache reset: before redoing reset errItems = %v", c.errItems) 594 for itemName := range c.errItems { 595 if retryItem, ok := c.item[itemName]; ok { 596 _, _, err := retryItem.Reset() 597 if err == nil || !fserrors.IsErrNoSpace(err) { 598 // TODO: not trying to handle non-ENOSPC errors yet 599 delete(c.errItems, itemName) 600 } 601 } else { 602 // The retry item was deleted because it was closed. 603 // No need to redo the failed reset now. 604 delete(c.errItems, itemName) 605 } 606 } 607 fs.Debugf(nil, "vfs cache reset: after redoing reset errItems = %v", c.errItems) 608 } 609 } 610 611 // Remove cache files that are not dirty until the quota is satisfied 612 func (c *Cache) purgeClean() { 613 c.mu.Lock() 614 defer c.mu.Unlock() 615 616 if c.quotasOK() { 617 return 618 } 619 620 var items Items 621 622 // Make a slice of clean cache files 623 for _, item := range c.item { 624 if !item.IsDirty() { 625 items = append(items, item) 626 } 627 } 628 629 sort.Sort(items) 630 631 // Reset items until the quota is OK 632 for _, item := range items { 633 if c.quotasOK() { 634 break 635 } 636 resetResult, spaceFreed, err := item.Reset() 637 // The item space might be freed even if we get an error after the cache file is removed 638 // The item will not be removed or reset if the cache data is dirty (DataDirty) 639 c.used -= spaceFreed 640 fs.Infof(nil, "vfs cache purgeClean item.Reset %s: %s, freed %d bytes", item.GetName(), resetResult.String(), spaceFreed) 641 if resetResult == RemovedNotInUse { 642 delete(c.item, item.name) 643 } 644 if err != nil { 645 fs.Errorf(nil, "vfs cache purgeClean item.Reset %s reset failed, err = %v, freed %d bytes", item.GetName(), err, spaceFreed) 646 c.errItems[item.name] = err 647 } 648 } 649 650 // Reset outOfSpace without checking whether we have reduced cache space below the quota. 651 // This allows some files to reduce their pendingAccesses count to allow them to be reset 652 // in the next iteration of the purge cleaner loop. 653 654 c.outOfSpace = false 655 c.cond.Broadcast() 656 } 657 658 // purgeOld gets rid of any files that are over age 659 func (c *Cache) purgeOld(maxAge time.Duration) { 660 c.mu.Lock() 661 defer c.mu.Unlock() 662 // cutoff := time.Now().Add(-maxAge) 663 for _, item := range c.item { 664 c.removeNotInUse(item, maxAge, false) 665 } 666 if c.quotasOK() { 667 c.outOfSpace = false 668 c.cond.Broadcast() 669 } 670 } 671 672 // Purge any empty directories 673 func (c *Cache) purgeEmptyDirs(dir string, leaveRoot bool) { 674 ctx := context.Background() 675 err := operations.Rmdirs(ctx, c.fcache, dir, leaveRoot) 676 if err != nil { 677 fs.Errorf(c.fcache, "vfs cache: failed to remove empty directories from cache path %q: %v", dir, err) 678 } 679 err = operations.Rmdirs(ctx, c.fcacheMeta, dir, leaveRoot) 680 if err != nil { 681 fs.Errorf(c.fcache, "vfs cache: failed to remove empty directories from metadata cache path %q: %v", dir, err) 682 } 683 } 684 685 // updateUsed updates c.used so it is accurate 686 func (c *Cache) updateUsed() (used int64) { 687 c.mu.Lock() 688 defer c.mu.Unlock() 689 690 newUsed := int64(0) 691 for _, item := range c.item { 692 newUsed += item.getDiskSize() 693 } 694 c.used = newUsed 695 return newUsed 696 } 697 698 // Check the available space for a disk is in limits. 699 func (c *Cache) minFreeSpaceQuotaOK() bool { 700 if c.opt.CacheMinFreeSpace <= 0 { 701 return true 702 } 703 du, err := diskusage.New(config.GetCacheDir()) 704 if err == diskusage.ErrUnsupported { 705 return true 706 } 707 if err != nil { 708 fs.Errorf(nil, "disk usage returned error: %v", err) 709 return true 710 } 711 return du.Available >= uint64(c.opt.CacheMinFreeSpace) 712 } 713 714 // Check the available quota for a disk is in limits. 715 // 716 // must be called with mu held. 717 func (c *Cache) maxSizeQuotaOK() bool { 718 if c.opt.CacheMaxSize <= 0 { 719 return true 720 } 721 return c.used <= int64(c.opt.CacheMaxSize) 722 } 723 724 // Check the available quotas for a disk is in limits. 725 // 726 // must be called with mu held. 727 func (c *Cache) quotasOK() bool { 728 return c.maxSizeQuotaOK() && c.minFreeSpaceQuotaOK() 729 } 730 731 // Return true if any quotas set 732 func (c *Cache) haveQuotas() bool { 733 return c.opt.CacheMaxSize > 0 || c.opt.CacheMinFreeSpace > 0 734 } 735 736 // Remove clean cache files that are not open until the total space 737 // is reduced below quota starting from the oldest first 738 func (c *Cache) purgeOverQuota() { 739 c.updateUsed() 740 741 c.mu.Lock() 742 defer c.mu.Unlock() 743 744 if c.quotasOK() { 745 return 746 } 747 748 var items Items 749 750 // Make a slice of unused files 751 for _, item := range c.item { 752 if !item.inUse() { 753 items = append(items, item) 754 } 755 } 756 757 sort.Sort(items) 758 759 // Remove items until the quota is OK 760 for _, item := range items { 761 c.removeNotInUse(item, 0, c.quotasOK()) 762 } 763 if c.quotasOK() { 764 c.outOfSpace = false 765 c.cond.Broadcast() 766 } 767 } 768 769 // clean empties the cache of stuff if it can 770 func (c *Cache) clean(kicked bool) { 771 // Cache may be empty so end 772 _, err := os.Stat(c.root) 773 if os.IsNotExist(err) { 774 return 775 } 776 c.updateUsed() 777 c.mu.Lock() 778 oldItems, oldUsed := len(c.item), fs.SizeSuffix(c.used) 779 c.mu.Unlock() 780 781 // Remove any files that are over age 782 c.purgeOld(c.opt.CacheMaxAge) 783 784 // If have a maximum cache size... 785 if c.haveQuotas() { 786 // Remove files not in use until cache size is below quota starting from the oldest first 787 c.purgeOverQuota() 788 789 // Remove cache files that are not dirty if we are still above the max cache size 790 c.purgeClean() 791 c.retryFailedResets() 792 } 793 794 // Was kicked? 795 if kicked { 796 c.kickerMu.Lock() // Make sure this is called with cache mutex unlocked 797 // Re-enable io threads to kick me 798 c.cleanerKicked = false 799 c.kickerMu.Unlock() 800 } 801 802 // Stats 803 c.mu.Lock() 804 newItems, newUsed := len(c.item), fs.SizeSuffix(c.used) 805 totalInUse := 0 806 for _, item := range c.item { 807 if item.inUse() { 808 totalInUse++ 809 } 810 } 811 c.mu.Unlock() 812 uploadsInProgress, uploadsQueued := c.writeback.Stats() 813 814 stats := fmt.Sprintf("objects %d (was %d) in use %d, to upload %d, uploading %d, total size %v (was %v)", 815 newItems, oldItems, totalInUse, uploadsQueued, uploadsInProgress, newUsed, oldUsed) 816 fs.Infof(nil, "vfs cache: cleaned: %s", stats) 817 if err = systemd.UpdateStatus(fmt.Sprintf("[%s] vfs cache: %s", time.Now().Format("15:04"), stats)); err != nil { 818 fs.Errorf(nil, "vfs cache: updating systemd status with current stats failed: %s", err) 819 } 820 } 821 822 // cleaner calls clean at regular intervals and upon being kicked for out-of-space condition 823 // 824 // doesn't return until context is cancelled 825 func (c *Cache) cleaner(ctx context.Context) { 826 if c.opt.CachePollInterval <= 0 { 827 fs.Debugf(nil, "vfs cache: cleaning thread disabled because poll interval <= 0") 828 return 829 } 830 // Start cleaning the cache immediately 831 c.clean(false) 832 // Then every interval specified 833 timer := time.NewTicker(c.opt.CachePollInterval) 834 defer timer.Stop() 835 for { 836 select { 837 case <-c.kick: // a thread encountering ENOSPC kicked me 838 c.clean(true) // kicked is true 839 case <-timer.C: 840 c.clean(false) // timer driven cache poll, kicked is false 841 case <-ctx.Done(): 842 fs.Debugf(nil, "vfs cache: cleaner exiting") 843 return 844 } 845 } 846 } 847 848 // TotalInUse returns the number of items in the cache which are InUse 849 func (c *Cache) TotalInUse() (n int) { 850 c.mu.Lock() 851 defer c.mu.Unlock() 852 for _, item := range c.item { 853 if item.inUse() { 854 n++ 855 } 856 } 857 return n 858 } 859 860 // Dump the cache into a string for debugging purposes 861 func (c *Cache) Dump() string { 862 if c == nil { 863 return "Cache: <nil>\n" 864 } 865 c.mu.Lock() 866 defer c.mu.Unlock() 867 var out strings.Builder 868 out.WriteString("Cache{\n") 869 for name, item := range c.item { 870 fmt.Fprintf(&out, "\t%q: %+v,\n", name, item) 871 } 872 out.WriteString("}\n") 873 return out.String() 874 } 875 876 // AddVirtual adds a virtual directory entry by calling the addVirtual 877 // callback if one has been registered. 878 func (c *Cache) AddVirtual(remote string, size int64, isDir bool) error { 879 if c.avFn == nil { 880 return errors.New("no AddVirtual function registered") 881 } 882 return c.avFn(remote, size, isDir) 883 }