Repository navigation
Conversation
`data(maxSize:)`, `putDataAsync`, `putFileAsync` and `writeAsync` ignored Swift task cancellation, so the transfer kept running and the caller kept waiting for it. Start the transfer inside `withTaskCancellationHandler` and cancel the Storage task when the calling task is cancelled, which stops the request and throws `StorageError.cancelled`. A lock-protected canceller also handles cancellation that arrives before the Storage task exists. Deliver results through the completion handler APIs, which register their observers before the task starts, instead of adding success and failure observers after starting it. Fixes #11786
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces Swift task cancellation support to FirebaseStorage's asynchronous APIs, including data(maxSize:), putDataAsync, putFileAsync, and writeAsync. When a calling Swift task is cancelled, the underlying storage operation is cancelled and a StorageError.cancelled error is thrown. This behavior is managed by a new thread-safe StorageTaskCanceller helper class and the withCancellableStorageTask function. Additionally, unit tests have been added to verify cancellation and success paths. No review comments were provided, so there is no feedback to address.
Problem
data(maxSize:),putDataAsync,putFileAsync, andwriteAsyncwrap the callback APIs inwithCheckedThrowingContinuationand ignore Swift task cancellation. Cancelling the calling task(for example, when the view of a SwiftUI
.taskmodifier disappears) doesn't stop the transfer:an upload still completes, and the caller stays suspended until the transfer finishes. These
APIs don't return the
StorageTask, so callers can't cancel the transfer themselves.An earlier attempt, #8529, stalled because
onCancelcan run before the Storage task exists,and the fix proposed there needed new public API.
Change
withTaskCancellationHandler.onCancelcallscancel()on theStorageUploadTaskorStorageDownloadTask, which stops the request andfails the task with
StorageError.cancelled, and that error is thrown to the caller.StorageTaskCancellercovers every ordering. IfonCancelruns beforethe task is created (it runs first when the calling task is already cancelled), the task is
cancelled as soon as it's created.
failure observers before the task starts. The
onProgressvariants used to add.successand.failureobservers after starting the task. Now they only add a.progressobserver, whichalso takes them off the late-observer path in Storage task observers can receive duplicate terminal callbacks #16813 (fixed for all callers by fix(storage): deliver terminal task events to late observers only once #16820).
cancel()does nothing once a task has succeeded or failed, so a late cancellation can'tresume the continuation a second time.
private.getMetadata,updateMetadata,delete,list, anddownloadURLare unchanged, since their internal tasks have nocancel().Testing
Added
StorageAsyncAwaitTests:data(maxSize:)(cancelled during the request, and with analready-cancelled task),
putDataAsyncwith and withoutonProgress,putFileAsync, andwriteAsync. Each uses a test block that never responds and cancels the calling task once therequest has started. It checks that
StorageError.cancelledis thrown within 10 seconds andthat the request was stopped (
isFetching == false).onProgressis still called.Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "finished", and the fivethat cancel an in-flight request also fail with
The request was not stopped. The four successtests pass.
xcodebuild -scheme FirebaseStorageUnit test -destination 'platform=macOS':173 tests, 0 failures.
Note: existing leak after
cancel()While testing this, I confirmed an existing leak that this change makes easier to hit. After
StorageTask.cancel(), upload and download tasks are never deallocated, including the data passedto
putData. Today, only an explicitcancel()hits the leak. With this PR, cancelling the callingtask hits it too.
#16841 fixes the leak separately, so ideally it lands first. The two PRs only overlap in the
CHANGELOG.
Fixes #11786