Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First pass at Swift cancellation #8529

Closed
wants to merge 4 commits into from
Closed

First pass at Swift cancellation #8529

wants to merge 4 commits into from

Conversation

paulb777
Copy link
Member

@paulb777 paulb777 commented Aug 14, 2021

Investigate feasibility of Swift cancellation implementation as a wrapper over existing APIs.

We're going to put this PR on hold pending more customer input and additional Swift Concurrency evolution. In the meantime, cancellation continues to be available via the original API.

Thanks to @maksymmalyhin 's last commit, inspired by https://github.com/apple/swift-evolution/blob/main/proposals/0300-continuation.md#additional-examples, we believe we're on track to a solution. The race condition in the comments could be solved by exposing the FIRStorageUploadTask init API https://github.com/firebase/firebase-ios-sdk/blob/master/FirebaseStorage/Sources/FIRStorageReference.m#L227, call it before to get the task, and then pass the task into a modified putFile API. The two new public APIs could be prefixed with double underscores to indicate they're not for public consumption.

Beyond that, it is still an open question about how to implement cancellation for the auto-generated APIs from Objective C. I don't see anything about overriding or modifying the generated functions at https://github.com/apple/swift-evolution/blob/main/proposals/0297-concurrency-objc.md.

@google-oss-bot
Copy link

1 Warning
⚠️ Did you forget to add a changelog entry? (Add #no-changelog to the PR description to silence this warning.)

Generated by 🚫 Danger


if Task.isCancelled {
print("xxxxxxxxx")
continuation.resume(throwing: StorageError.cancelled)
Copy link
Member Author

Choose a reason for hiding this comment

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

This makes the test pass, but only works if the cancellation happens early.

print("\(progress.completedUnitCount) of \(progress.totalUnitCount)")
if Task.isCancelled {
print("zzzzzzzzzzzzz")
storageTask.cancel()
Copy link
Member Author

Choose a reason for hiding this comment

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

I can't figure out a test to get into this block.

Similar issue as https://forums.swift.org/t/how-can-i-get-the-current-task/50677

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess you may wrap the storage task into a parent task (I did not actually tested it myself):

let taskHandle = async { // Create a parent task.
  return try await storage.putFileAsync(...) // The task is created and started.
} 

taskHandle.cancel() // The storage task is being cancelled because the parent task has been cancelled. 

See "Unstructured Concurrency" here and cancel() method docs.

Another approach could be using a TaskGroup which has cancelAll method.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, but I'm not sure I follow. The first suggestion is how the invocation of the putFileAsync API is done in the test below. Here, I'm trying to figure out the implementation.

The challenge I'm having is finding a way to cancel the Objective C task that is running from GTMSessionFetcher. And I can't figure out a way to get a handle to the Swift task to be able to check if it has been cancelled to know that the Objective C task should be cancelled.

return metadata
}

let result = try await task.value
Copy link
Member Author

Choose a reason for hiding this comment

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

Is this the best way for a client to call the putFileAsync API so that they can have a handle to cancel it?

@@ -193,6 +193,26 @@ class StorageResultTests: StorageIntegrationCommon {
waitForExpectations()
}

func testSimplePutFileCancel() throws {
Copy link
Member Author

Choose a reason for hiding this comment

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

Add a test for cancellation with the old APIs

continuation.resume(with: result)

let metadata =
try await withCheckedThrowingContinuation { (continuation: MetadataContinuation) in
Copy link
Member Author

Choose a reason for hiding this comment

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

There is withTaskCancellationHandler but I'm not sure how to combine that with withCheckedThrowingContinuation and not sure it would work with cancelling the Objective C task anyway.

https://forums.swift.org/t/how-to-cancel-a-publisher-when-using-withtaskcancellationhandler/49688/5

@paulb777 paulb777 marked this pull request as draft August 14, 2021 15:38
@google-oss-bot
Copy link

google-oss-bot commented Aug 14, 2021

Coverage Report

Affected SDKs

No changes between base commit (125a6fe) and head commit (2622a5c).

Test Logs

@paulb777
Copy link
Member Author

paulb777 commented Sep 2, 2022

Succeeded by #10161

@paulb777 paulb777 closed this Sep 2, 2022
@paulb777 paulb777 deleted the pb-swift-task branch September 2, 2022 22:46
@firebase firebase locked and limited conversation to collaborators Oct 3, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants