Fix StorageUploadTask cancel - #16359
Conversation
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 aims to resolve race conditions in Storage task cancellation and improve Swift 6 strict concurrency compliance by introducing NSLock (stateLock) and adopting @unchecked Sendable across various StorageTask subclasses. While these changes are a step in the right direction, the code review highlights several critical issues that must be addressed. Specifically, calling fetcher control methods (such as stopFetching, pauseFetching, and resumeFetching) while holding the stateLock poses a high risk of deadlocks. Additionally, there are multiple instances of data races where shared properties (like fetcher, uploadFetcher, downloadData, and state maps) are accessed or mutated outside of the lock. Finally, some synchronization can be simplified, such as removing redundant locking when iterating over local copies of handler dictionaries.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses race conditions in Firebase Storage task cancellation and improves Swift 6 strict concurrency compliance by introducing NSLock synchronization and marking tasks as @unchecked Sendable. However, the review highlights several critical concurrency issues: a return statement inside a lock closure in resume() fails to prevent task enqueuing; race conditions exist when pausing or cancelling tasks in the .queueing state; paused tasks can trigger spurious failure events because the catch blocks do not check for paused states; and potential data races remain due to unsynchronized reads of handlerDictionaries.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses a race condition in Storage task cancellation and improves Swift 6 strict concurrency compliance by introducing a stateLock (NSLock) to protect mutable task state and marking tasks as @unchecked Sendable. The review feedback highlights several remaining data races where properties like state, fetcher, and uploadFetcher are accessed outside of the lock in StorageObservableTask, StorageDownloadTask, and StorageUploadTask. The reviewer suggests capturing these properties locally within the lock or using existing local variables to ensure complete thread safety.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses race conditions in Storage task cancellation and improves Swift 6 strict concurrency compliance by introducing stateLock to protect mutable state across various StorageTask subclasses, which are now marked as @unchecked Sendable. It also adds integration tests for upload cancellation. The review feedback highlights two remaining data races where state in StorageObservableTask and fetcher in StorageDownloadTask are accessed outside of the stateLock, and provides actionable suggestions to resolve them.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses race conditions in Storage task cancellation and improves Swift 6 strict concurrency compliance by introducing a stateLock (NSLock) and marking tasks as @unchecked Sendable. The review feedback highlights several critical state-management issues where task states (such as .cancelled, .paused, .success, or .failed) could be incorrectly overwritten or where duplicate observer events could be fired. Specifically, registering a new observer might re-trigger all existing observers, and asynchronous task execution or late-dispatched progress/resume blocks could resurrect or overwrite terminal states.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses race conditions in Storage task cancellation and improves Swift 6 strict concurrency compliance by introducing stateLock (NSLock) and @unchecked Sendable across various StorageTask classes. The review feedback highlights several critical race conditions and state-corruption risks in the new locking logic. Specifically, late-firing progress or pause callbacks in StorageDownloadTask and StorageUploadTask could erroneously revert completed or failed tasks back to .running or .paused states. Additionally, a race condition in StorageObservableTask between capturing a snapshot and registering an observer could cause observers to miss status notifications. The reviewer recommends atomic observer registration and stricter state validation before state transitions.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request improves Swift 6 strict concurrency compliance and thread safety across Firebase Storage tasks by replacing objc_sync_enter/exit with an explicit NSLock (stateLock) and marking tasks as @unchecked Sendable. While these changes resolve several race conditions, the review highlights critical issues in StorageUploadTask and StorageInternalTask. Specifically, StorageUploadTask.enqueue() can unconditionally overwrite a cancelled or paused state, and its resume() method fails to recover if the task was paused before the fetcher was initialized. Additionally, a potential data race remains in StorageInternalTask where the fetcher property is accessed concurrently without synchronization.
|
/gemini review |
|
Warning Gemini encountered an error creating the review. You can try again by commenting |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses a race condition in Storage task cancellation and improves Swift 6 strict concurrency compliance by replacing Objective-C synchronization with a dedicated NSLock (stateLock) and marking storage tasks as @unchecked Sendable. The reviewer provided valuable feedback pointing out three issues: in StorageDownloadTask, the final progress updates in both the success and error paths are fired with incorrect statuses because the state is not temporarily transitioned to .progress under the lock; and in StorageUploadTask, calling contentUploadError() inside the lock performs synchronous file I/O and could block other threads, so it should be moved outside the lock.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses race conditions in Storage task cancellation and improves Swift 6 strict concurrency compliance by introducing an NSLock (stateLock) to protect mutable state across various StorageTask subclasses. It also marks these tasks as @unchecked Sendable and replaces Objective-C synchronization blocks with lock-based synchronization. Additionally, integration tests for cancelling uploads have been added. The review feedback suggests optimizing lock usage in both StorageDownloadTask and StorageUploadTask by capturing the task's paused/pausing state within the initial lock block, which avoids unnecessary lock re-acquisition and ensures better atomicity.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses race conditions in Storage task cancellation and improves Swift 6 strict concurrency compliance by introducing stateLock (NSLock) to protect mutable state across StorageTask subclasses, marking them as @unchecked Sendable. It also adds integration tests for upload cancellation. The review feedback highlights two critical issues: first, calling pause() on StorageDownloadTask before the fetcher is initialized can cause the task to get stuck in the .pausing state; second, a JSON parsing failure in StorageUploadTask incorrectly transitions the task to a .success state despite setting an error. Both issues should be resolved using the provided code suggestions.
|
/gemini review |
|
Warning Gemini encountered an error creating the review. You can try again by commenting |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses a race condition in Storage task cancellation and improves Swift 6 strict concurrency compliance for StorageTask by introducing a stateLock (NSLock) to protect mutable state across various tasks (StorageInternalTask, StorageDownloadTask, StorageUploadTask, StorageObservableTask). It marks these classes as @unchecked Sendable and updates state transitions and observer handling to be thread-safe. Additionally, integration tests are added to verify task cancellation during uploads. The review feedback suggests capturing the local fetcher weakly in receivedProgressBlock to avoid a potential retain cycle and unnecessary synchronized reads, and recommends using the more idiomatic UUID().uuidString instead of NSUUID().uuidString in Swift.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request addresses a race condition in Storage task cancellation and improves Swift 6 strict concurrency compliance for StorageTask. It replaces objc_sync_enter/objc_sync_exit with a dedicated NSLock (stateLock) across StorageTask and its subclasses (StorageInternalTask, StorageDownloadTask, StorageUploadTask, and StorageObservableTask) to safely synchronize access to mutable state, progress, and observers. Additionally, these classes are marked as @unchecked Sendable. Integration tests have been added to verify upload cancellation in both Swift and Objective-C. There are no review comments, and I have no feedback to provide.
|
I got your ping @paulb777 👋 let me give it a try |
|
I took the affected files from this PR and patched them into a worktree where I re-enabled the test I temporarily disabled, ran it, and it appears I can now reliably cancel StorageUploadTasks on iOS ✅ ✔ successfully cancels a download |
morganchen12
left a comment
There was a problem hiding this comment.
LGTM with an optional suggestion.
Fix #16353
Overview
This pull request addresses several critical race conditions in Firebase Storage task cancellation and significantly improves Swift 6 strict concurrency compliance. By introducing robust locking mechanisms and fixing logic flaws in task state management, we ensure reliable execution, proper cancellation, and safe concurrency across the Firebase Storage module.
Key Changes
1. Swift 6 Concurrency Compliance & Synchronization
stateLock(NSLock) Migration: Replaced the legacy Objective-C synchronization (objc_sync_enter/objc_sync_exit) with a dedicatedNSLock(stateLock) acrossStorageTasksubclasses to protect mutable state and ensure thread safety.@unchecked SendableAdherence: Explicitly marked tasks (StorageDownloadTask,StorageUploadTask,StorageObservableTask,StorageInternalTask) as@unchecked Sendableto resolve Swift 6 strict concurrency warnings while manually managing cross-actor state synchronization.handlerDictionariesinStorageObservableTask) by ensuring all accesses are protected bystateLock.withLock.2. Task State and Data Race Fixes
StorageDownloadTaskandStorageUploadTaskwhere late-firingprogress,pause, orresumecallbacks could erroneously revert a task out of a terminal state (.success,.failed, or.cancelled), leading to corrupted states and deadlocks.stateproperty directly toStorageTaskSnapshotto perfectly capture the rawStorageTaskStateat the moment of creation. This completely eliminates unsynchronized reads of the task's internal state when firing observer callbacks.StorageInternalTaskFetcher Concurrency: Fixed a data race where the privatefetcherproperty was being written to and read concurrently. It is now properly synchronized withstateLock.withLock, and execution uses a thread-safe local reference.3. Observer Event Duplication
observe(_:handler:)inStorageObservableTaskto perform both the state snapshot creation and the dictionary registration atomically under a single lock. This prevents race conditions where a task changes state mid-registration, dropping events.4. Task Enqueueing and Resume Logic
StorageUploadTask.resume()where a task paused before its fetcher was allocated would fail to resume. The method now detects the missing fetcher and correctly re-queues the upload.StorageUploadTask.enqueue()from blindly forcing tasks into the.queueingstate if they had already been successfully paused or cancelled while resting on the dispatch queue.enqueueImplementationlogic inStorageDownloadTaskto accurately check a boolean flag and abort if the task was cancelled prior to the asynchronous task execution hopping threads.Testing
testCancelErrorCodeandtestCancelUpload.pb-fix-storage-upload-cancel(in bothFirebaseStorage/Tests/IntegrationandFirebaseStorage/Tests/ObjCIntegration).