-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Concurrency] Avoid inserting handler record in already cancelled task. #80456
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -244,6 +244,16 @@ void removeStatusRecordWhere( | |
llvm::function_ref<bool(ActiveTaskStatus, TaskStatusRecord*)> condition, | ||
llvm::function_ref<void(ActiveTaskStatus, ActiveTaskStatus&)>updateStatus = nullptr); | ||
|
||
/// Remove and return a status record of the given type. This function removes a | ||
/// singlw record, and leaves subsequent records as-is if there are any. | ||
/// Returns `nullptr` if there are no matching records. | ||
/// | ||
/// NOTE: When using this function with new record types, make sure to provide | ||
/// an explicit instantiation in TaskStatus.cpp. | ||
template <typename TaskStatusRecordT> | ||
SWIFT_CC(swift) | ||
TaskStatusRecordT* popStatusRecordOfType(AsyncTask *task); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may want to move the templated parts of the implementation into the header to avoid annoying link issues. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did try that but we don’t have the types fully declared there hmmm, I can try again. I’ll share the error There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's fine to leave it if C++ objects too strenuously to my idea. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah it's a bit painful, we'd need complete type declarations for ActiveTaskStatus which is somewhat annoying to pull up completely.
I'll leave as is I think but thank you very much for review! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could have a small templatized implementation in the header that just retrieves the kind of the type being requested, then calls into a real non-templatized implementation which looks for that kind. But we can save that for when we actually need it. |
||
|
||
/// Remove a status record from the current task. This must be called | ||
/// synchronously with the task. | ||
SWIFT_CC(swift) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -target %target-swift-5.1-abi-triple %import-libdispatch) | %FileCheck %s | ||
// REQUIRES: concurrency | ||
// REQUIRES: executable_test | ||
|
||
// rdar://76038845 | ||
// REQUIRES: concurrency_runtime | ||
// UNSUPPORTED: back_deployment_runtime | ||
// UNSUPPORTED: freestanding | ||
|
||
import Synchronization | ||
|
||
struct State { | ||
var cancelled = 0 | ||
var continuation: CheckedContinuation<Void, Never>? | ||
} | ||
|
||
func testFunc(_ iteration: Int) async -> Task<Void, Never> { | ||
let state = Mutex(State()) | ||
|
||
let task = Task { | ||
await withTaskCancellationHandler { | ||
await withCheckedContinuation { continuation in | ||
let cancelled = state.withLock { | ||
if $0.cancelled > 0 { | ||
return true | ||
} else { | ||
$0.continuation = continuation | ||
return false | ||
} | ||
} | ||
if cancelled { | ||
continuation.resume() | ||
} | ||
} | ||
} onCancel: { | ||
let continuation = state.withLock { | ||
$0.cancelled += 1 | ||
return $0.continuation.take() | ||
} | ||
continuation?.resume() | ||
} | ||
} | ||
|
||
// This task cancel is racing with installing the cancellation handler, | ||
// and we may either hit the cancellation handler: | ||
// - after this cancel was issued, and therefore the handler runs immediately | ||
task.cancel() | ||
_ = await task.value | ||
|
||
let cancelled = state.withLock { $0.cancelled } | ||
precondition(cancelled == 1, "cancelled more than once, iteration: \(iteration)") | ||
|
||
return task | ||
} | ||
|
||
var ts: [Task<Void, Never>] = [] | ||
for iteration in 0..<1_000 { | ||
let t = await testFunc(iteration) | ||
ts.append(t) | ||
} | ||
|
||
print("done") // CHECK: done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way the remove and add are paired is such that the record should always be at the top here, so this removal should be exactly that record on the top.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to add an assert here that
task->_private()._status().load(std::memory_order_relaxed).getInnermostRecord() == record
to check that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ll add that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding here #80522