drime: fix directory rename leaving the renamed folder empty in VFS

Drime's folder rename returns success immediately but the children
listing of the renamed folder briefly returns empty before settling,
which made VFS see an empty directory right after a successful rename
and broke subsequent operations on its contents.

After a successful DirMove, poll the renamed folder's listing until it
shows the pre-rename child count, with a 30s timeout and exponential
backoff. The backend integration test already tolerated this via its
own list-consistency retries; this brings VFS into line.

Fixes #9450
This commit is contained in:
Nick Craig-Wood
2026-05-29 12:51:26 +01:00
parent d742334be5
commit 387be8d8c0
+57 -1
View File
@@ -927,6 +927,48 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object,
return dstObj, nil
}
// countDirChildren returns the number of direct children of dirID.
func (f *Fs) countDirChildren(ctx context.Context, dirID string) (int, error) {
n := 0
_, err := f.listAll(ctx, dirID, false, false, "", func(*api.Item) bool {
n++
return false
})
return n, err
}
// waitForDirChildren polls dirID until it lists at least want direct
// children, or the timeout elapses. Drime's folder rename has visible
// eventual-consistency on the children listing - the rename returns
// success immediately but the renamed folder lists as empty for a
// short window - so callers that rename a folder use this to wait for
// the listing to settle before returning (see issue #9450).
func (f *Fs) waitForDirChildren(ctx context.Context, dirID string, want int) error {
const timeout = 30 * time.Second
deadline := time.Now().Add(timeout)
backoff := 100 * time.Millisecond
for {
got, err := f.countDirChildren(ctx, dirID)
if err != nil {
return err
}
if got >= want {
return nil
}
if time.Now().After(deadline) {
return fmt.Errorf("timed out waiting for directory listing to settle: got %d, want %d", got, want)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(backoff):
}
if backoff < 2*time.Second {
backoff *= 2
}
}
}
// DirMove moves src, srcRemote to this remote at dstRemote
// using server-side move operations.
//
@@ -947,11 +989,25 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
return err
}
// Count children before the move so we can wait for the server's
// children listing to reflect the rename - see waitForDirChildren.
preCount, err := f.countDirChildren(ctx, srcID)
if err != nil {
return fmt.Errorf("DirMove: count source children: %w", err)
}
// Do the move
_, err = f.moveTo(ctx, srcID, srcLeaf, dstLeaf, srcDirectoryID, dstDirectoryID)
info, err := f.moveTo(ctx, srcID, srcLeaf, dstLeaf, srcDirectoryID, dstDirectoryID)
if err != nil {
return err
}
if preCount > 0 {
if err := f.waitForDirChildren(ctx, info.ID.String(), preCount); err != nil {
fs.Logf(f, "DirMove: %v (continuing)", err)
}
}
srcFs.dirCache.FlushDir(srcRemote)
return nil
}