diff --git a/backend/dropbox/dropbox.go b/backend/dropbox/dropbox.go index 3a48c2310..b43f0876c 100644 --- a/backend/dropbox/dropbox.go +++ b/backend/dropbox/dropbox.go @@ -2106,6 +2106,11 @@ func (o *Object) uploadChunked(ctx context.Context, in0 io.Reader, commitInfo *f } } } + // Don't waste the low level retries if the context has + // been cancelled + if fserrors.ContextError(ctx, &err) { + return false, err + } return err != nil, err }) if err != nil { diff --git a/backend/dropbox/dropbox_internal_test.go b/backend/dropbox/dropbox_internal_test.go index 8c8a9a2f4..fe2f46fa1 100644 --- a/backend/dropbox/dropbox_internal_test.go +++ b/backend/dropbox/dropbox_internal_test.go @@ -151,6 +151,7 @@ type uploadSessionClient struct { maxAppends int // fail the append after this many calls to stop runaway loops bytesWritten int64 // bytes received by UploadSessionAppendV2Context finishCalled bool // set if UploadSessionFinishContext was called + appended func() // if set, called after each successful append } var errTooManyAppends = errors.New("too many appends - upload looping?") @@ -173,6 +174,9 @@ func (c *uploadSessionClient) UploadSessionAppendV2Context(ctx context.Context, return err } c.bytesWritten += n + if c.appended != nil { + c.appended() + } return nil } @@ -197,6 +201,16 @@ func newUploadTestFs(t *testing.T, srv files.ContextClient, chunkSize fs.SizeSuf return f } +// endlessReader supplies bytes forever +type endlessReader struct{} + +func (endlessReader) Read(p []byte) (int, error) { + for i := range p { + p[i] = 'x' + } + return len(p), nil +} + func TestUploadChunkedEarlyEOF(t *testing.T) { ctx := context.Background() @@ -235,6 +249,25 @@ func TestUploadChunkedEarlyEOF(t *testing.T) { }) } +func TestUploadChunkedCancel(t *testing.T) { + // Cancelling the context must stop the upload even though every + // append is succeeding + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + client := &uploadSessionClient{maxAppends: 8} + client.appended = func() { + if client.appends == 2 { + cancel() + } + } + f := newUploadTestFs(t, client, 100) + o := &Object{fs: f, remote: "test.bin"} + _, err := o.uploadChunked(ctx, endlessReader{}, files.NewCommitInfo("/test.bin"), -1) + require.Error(t, err) + assert.ErrorIs(t, err, context.Canceled) + assert.False(t, client.finishCalled) +} + func (f *Fs) importPaperForTest(t *testing.T) { content := `# test doc