Skip to content

Fix StorageUploadTask cancel - #16359

Merged
paulb777 merged 22 commits into
mainfrom
pb-fix-storage-upload-cancel2
Jul 9, 2026
Merged

Fix StorageUploadTask cancel#16359
paulb777 merged 22 commits into
mainfrom
pb-fix-storage-upload-cancel2

Conversation

@paulb777

@paulb777 paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member

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 dedicated NSLock (stateLock) across StorageTask subclasses to protect mutable state and ensure thread safety.
  • @unchecked Sendable Adherence: Explicitly marked tasks (StorageDownloadTask, StorageUploadTask, StorageObservableTask, StorageInternalTask) as @unchecked Sendable to resolve Swift 6 strict concurrency warnings while manually managing cross-actor state synchronization.
  • Synchronized Dictionary Access: Fixed data races when reading observer handler dictionaries (e.g., handlerDictionaries in StorageObservableTask) by ensuring all accesses are protected by stateLock.withLock.

2. Task State and Data Race Fixes

  • Terminal State Preservation: Fixed critical bugs in StorageDownloadTask and StorageUploadTask where late-firing progress, pause, or resume callbacks could erroneously revert a task out of a terminal state (.success, .failed, or .cancelled), leading to corrupted states and deadlocks.
  • Synchronized State Snapshots: Added an internal state property directly to StorageTaskSnapshot to perfectly capture the raw StorageTaskState at the moment of creation. This completely eliminates unsynchronized reads of the task's internal state when firing observer callbacks.
  • StorageInternalTask Fetcher Concurrency: Fixed a data race where the private fetcher property was being written to and read concurrently. It is now properly synchronized with stateLock.withLock, and execution uses a thread-safe local reference.

3. Observer Event Duplication

  • Atomic Registration: Refactored observe(_:handler:) in StorageObservableTask to 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.
  • Duplicate Callbacks Eliminated: Addressed logic that previously fired all registered handlers simultaneously upon attaching a new observer. Now, the method cleanly guarantees that only the newly attached callback triggers for a previously-achieved state.

4. Task Enqueueing and Resume Logic

  • No-Op Resume Fix: Fixed a deadlock in 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.
  • Enqueue Override Protection: Prevented StorageUploadTask.enqueue() from blindly forcing tasks into the .queueing state if they had already been successfully paused or cancelled while resting on the dispatch queue.
  • Task Hop Bypasses Fixed: Updated enqueueImplementation logic in StorageDownloadTask to accurately check a boolean flag and abort if the task was cancelled prior to the asynchronous task execution hopping threads.

Testing

  • All integration tests pass successfully.
  • Addressed failures related to cancellation assertions in testCancelErrorCode and testCancelUpload.
  • Added and verified integration testing patterns from pb-fix-storage-upload-cancel (in both FirebaseStorage/Tests/Integration and FirebaseStorage/Tests/ObjCIntegration).
  • Verified successful compilation without warnings under strict concurrency checks.

@gemini-code-assist

Copy link
Copy Markdown
Contributor
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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.

@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift Outdated
Comment thread FirebaseStorage/Sources/Internal/StorageInternalTask.swift
Comment thread FirebaseStorage/Sources/StorageObservableTask.swift Outdated
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageObservableTask.swift Outdated
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageObservableTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageObservableTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageObservableTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift
Comment thread FirebaseStorage/Sources/StorageObservableTask.swift Outdated
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageUploadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift
Comment thread FirebaseStorage/Sources/Internal/StorageInternalTask.swift
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift
Comment thread FirebaseStorage/Sources/StorageUploadTask.swift
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Comment thread FirebaseStorage/Sources/StorageObservableTask.swift Outdated
@paulb777

paulb777 commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@mikehardy

Copy link
Copy Markdown
Contributor

I got your ping @paulb777 👋 let me give it a try

@mikehardy

Copy link
Copy Markdown
Contributor

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
✔ successfully cancels an upload
72 passing (28s), 5 pending
✨ Tests Complete ✨

@paulb777
paulb777 marked this pull request as ready for review July 8, 2026 03:46
@paulb777
paulb777 requested review from morganchen12 and ncooke3 July 8, 2026 03:47

@morganchen12 morganchen12 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with an optional suggestion.

Comment thread FirebaseStorage/Sources/StorageDownloadTask.swift Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FIRStorageUploadTask cancel during progress does not reach terminal cancelled state

3 participants