vfscache: fix grace timer reusing stale fd after _checkObject removes cache file

Before this change when a cache item was in its grace period (with
HandleCaching) and the file is reopened, _checkObject runs before the
grace timer recovery check. If the remote object's fingerprint changed
_checkObject removes the cache file from disk. However the grace
recovery path still reused the now-stale fd pointing to a deleted
inode, skipping _createFile entirely. This left no cache file on disk,
causing cache.Exists() to return false and breaking
rename-while-writing logic.

Fix this by checking the cache file still exists before reusing the fd
in grace recovery. If the file was removed, close the stale fd and
downloaders and fall through to _createFile.

Also update the fingerprint in item.rename after setting the new object,
preventing unnecessary cache invalidation when a file is reopened after
a rename.

This was discovered in the integration tests on backends that update
modtime on rename (like mailru).
This commit is contained in:
Nick Craig-Wood
2026-04-09 12:48:59 +01:00
parent 16591fdc21
commit bfd650b428

View File

@@ -541,9 +541,26 @@ func (item *Item) open(o fs.Object) (err error) {
if item.graceTimer != nil {
item.graceTimer.Stop()
item.graceTimer = nil
// fd and downloaders still alive - reuse them
// _checkObject already called above
return nil
// If the cache file still exists, reuse fd and downloaders.
// _checkObject (called above) may have removed the cache
// file if the remote fingerprint changed (e.g. modtime
// changed during a rename/move). In that case we must
// close the stale fd and fall through to _createFile.
if item._exists() {
return nil
}
fs.Debugf(item.name, "vfs cache: cache file vanished during grace period, recreating")
if item.fd != nil {
_ = item.fd.Close()
item.fd = nil
}
if item.downloaders != nil {
dls := item.downloaders
item.downloaders = nil
item.mu.Unlock()
_ = dls.Close(nil)
item.mu.Lock()
}
}
err = item._createFile(osPath)
@@ -1486,6 +1503,7 @@ func (item *Item) rename(name string, newName string, newObj fs.Object) (err err
// Set internal state
item.name = newName
item.o = newObj
item._updateFingerprint()
// Rename cache file if it exists
err = rename(item.c.toOSPath(name), item.c.toOSPath(newName)) // No locking in Cache