-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Check cancellation for task #3557
base: main
Are you sure you want to change the base?
Conversation
Hi @junjielu, can you provide a bit more context about this change? What problem did you notice that this fixes? And can you write a test that fails without this change and passes with? As far as I can tell they seem to be about equivalent as long as the work performed in |
@mbrandonw Yes, I think I should have gone into more detail. let taskA = try await withTaskCancellation(id: CancelID.A, cancelInFlight: true) {
let result = try await doSomeThing() // Method won't check cancellation.
print("Task A finished")
}
let taskB = try await withTaskCancellation(id: CancelID.B, cancelInFlight: true) {
try await Task.sleep(nanoseconds: 5 * NSEC_PER_SEC) // `sleep` will check for cancellations internally.
print("Task B finished")
}
let taskC = try await withTaskCancellation(id: CancelID.C, cancelInFlight: true) {
try await doSomeThing() // Method won't check cancellation.
print("doSomeThing finished")
let result = try await doSomeThingElse() // Method will check cancellation.
print("doSomeThingElse finished")
return result
}
...
Task.cancel(id: CancelID.A) // "Task A finished" still printed
Task.cancel(id: CancelID.B) // "Task B finished" will not print
Task.cancel(id: CancelID.C) // "doSomeThing finished" will print, but "doSomeThingElse finished" won't In this example you can see the significant difference, some async methods check for cancellation internally and some don't. So putting the matter of checking for cancellations inside the After: let taskA = try await withTaskCancellation(id: CancelID.A, cancelInFlight: true) {
let result = try await doSomeThing() // Method won't check cancellation.
print("Task A finished")
}
let taskB = try await withTaskCancellation(id: CancelID.B, cancelInFlight: true) {
try await Task.sleep(nanoseconds: 5 * NSEC_PER_SEC) // `sleep` will check for cancellations internally.
print("Task B finished")
}
let taskC = try await withTaskCancellation(id: CancelID.C, cancelInFlight: true) {
try await doSomeThing() // Method won't check cancellation.
print("doSomeThing finished")
let result = try await doSomeThingElse() // Method will check cancellation.
print("doSomeThingElse finished")
return result
}
...
Task.cancel(id: CancelID.A) // "Task A finished" won't printed
Task.cancel(id: CancelID.B) // "Task B finished" won't print
Task.cancel(id: CancelID.C) // Nothing will print |
Hi @junjielu, I still do not think there is anything to fix here. As long as the async work you are invoking inside However, even Swift's own async tools do not behave the way you want them to. They behave like our For example, consider a non-cooperative sleep function: func nonCooperativeSleep(duration: Duration) async throws {
await Task { try? await Task.sleep(for: duration) }.value
} This behaves like If you then use this non-cooperative sleep in a let task = Task<Int, any Error> {
try await nonCooperativeSleep(duration: .seconds(1)) // Does not cancel
return 42
}
try? await Task.sleep(for: .seconds(0.5)) // Allow time to start the task
task.cancel()
let value = try await task.value
print(value) // 42 …you will find that the value is returned from the task just fine and printed to the console. If we were to take the change that you are proposing, then it would be the case that we need to litter So, I personally believe that things are correct as they are right now. If there is something I am not understanding though please do let me know. |
Hi @mbrandonw , I can see what you mean, I think where we're not agreeing right now is whether I would think it depends on the expectations of the caller. For example with the function Then, when using try await withTaskCancellation(id: CancelID.Task, cancelInFlight: true) {
let valueA = try await nonCooperativeSleep(duration: .seconds(1))
try Task.checkCancellation()
let valueB = try await nonCooperativeSleep(duration: .seconds(2))
try Task.checkCancellation()
let valueC = try await nonCooperativeSleep(duration: .seconds(3))
try Task.checkCancellation()
return valueA + valueB + valueC // The above three check cancellations I think would make boilerplate code
} So it would be more in line with the coder's expectations if the cancellation error was thrown at the point of use, rather than checking for cancellation after every function that doesn't support task cancellation. |
I think it is necessary to check if a task will be cancelled in a task cancellation scenario. The current code needs to actively check for cancellation at the top level, which I don't think is a reasonable approach.
Before:
After: