The concurrent walker created by walk() only stopped when the callback returned an error or the whole tree had been listed. Cancelling the context (for example via the rc job/stop endpoint for an async operations/size or recursive operations/list call) was therefore ignored: the checkers kept pulling list jobs from the channel and kept listing the entire tree, burning CPU and making job cancellation useless for every backend without a native ListR implementation. Make every checker select on ctx.Done() so a cancelled walk shuts down promptly through the existing quit/drain path and reports the context error. Also check the context between directory read chunks in the local backend so a single huge directory does not block cancellation.
48 lines
1.1 KiB
Go
48 lines
1.1 KiB
Go
package walk
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fstest/mockdir"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// Test that walk stops listing when the context is cancelled.
|
|
func TestWalkContextCancelled(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
started := make(chan struct{}, 1)
|
|
listDir := func(ctx context.Context, f fs.Fs, includeAll bool, dir string) (entries fs.DirEntries, err error) {
|
|
select {
|
|
case started <- struct{}{}:
|
|
default:
|
|
}
|
|
// Every directory contains one subdirectory so the walk never
|
|
// finishes on its own.
|
|
return fs.DirEntries{mockdir.New("sub")}, nil
|
|
}
|
|
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- walk(ctx, nil, "", false, -1, func(path string, entries fs.DirEntries, err error) error {
|
|
return nil
|
|
}, listDir)
|
|
}()
|
|
|
|
// Wait for the walk to start.
|
|
<-started
|
|
|
|
cancel()
|
|
|
|
select {
|
|
case err := <-done:
|
|
assert.ErrorIs(t, err, context.Canceled)
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("walk did not stop after the context was cancelled")
|
|
}
|
|
}
|