Skip to content

Conversation

@ktoso
Copy link
Contributor

@ktoso ktoso commented Apr 2, 2025

This avoids the potential to race with the triggering coming from task_cancel, because we first set the cancelled flag, and only THEN take the lock and iterate over the inserted records. Because of this we could: T1 flip the cancelled bit; T2 observes that, and triggers "immediately" during installing the handler record. T1 then proceeds to lock records and trigger it again, causing a double trigger of the cancellation handler.

resolves #80161
resolves rdar://147493150

This avoids the potential to race with the triggering coming from
task_cancel, because we first set the cancelled flag, and only THEN
take the lock and iterate over the inserted records. Because of this we
could: T1 flip the cancelled bit; T2 observes that, and triggers
"immediately" during installing the handler record. T1 then proceeds to
lock records and trigger it again, causing a double trigger of the
cancellation handler.

resolves swiftlang#80161
resolves rdar://147493150
@ktoso
Copy link
Contributor Author

ktoso commented Apr 2, 2025

@swift-ci please test

@ktoso
Copy link
Contributor Author

ktoso commented Apr 2, 2025

@swift-ci please smoke test

@ktoso ktoso requested a review from mikeash April 2, 2025 03:37
return;
}

if (auto poppedRecord =
Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I’ll add that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding here #80522

@ktoso
Copy link
Contributor Author

ktoso commented Apr 2, 2025

Failure was TestSwiftActorUnprioritisedJobs.py which seems to be flaky, see also swiftlang/llvm-project#10404 trying to fix it

@ktoso
Copy link
Contributor Author

ktoso commented Apr 2, 2025

@swift-ci please smoke test macOS

@ktoso ktoso merged commit 28c4930 into swiftlang:main Apr 2, 2025
5 checks passed
@ktoso ktoso deleted the wip-cancel-race branch April 2, 2025 10:21
/// an explicit instantiation in TaskStatus.cpp.
template <typename TaskStatusRecordT>
SWIFT_CC(swift)
TaskStatusRecordT* popStatusRecordOfType(AsyncTask *task);
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

@ktoso ktoso Apr 4, 2025

Choose a reason for hiding this comment

The 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.

/Users/ktoso/code/swift-project/swift/stdlib/public/Concurrency/TaskPrivate.h:255:27: warning: extra qualification on member 'popStatusRecordOfType' [-Wextra-qualification]
  255 | TaskStatusRecordT* swift::popStatusRecordOfType(AsyncTask *task) {
      |                           ^
/Users/ktoso/code/swift-project/swift/stdlib/public/Concurrency/TaskPrivate.h:255:27: error: out-of-line definition of 'popStatusRecordOfType' does not match any declaration in namespace 'swift'
  255 | TaskStatusRecordT* swift::popStatusRecordOfType(AsyncTask *task) {
      |                           ^~~~~~~~~~~~~~~~~~~~~
/Users/ktoso/code/swift-project/swift/stdlib/public/Concurrency/TaskPrivate.h:258:54: error: variable has incomplete type 'ActiveTaskStatus'
  258 |   removeStatusRecordWhere(task, [&](ActiveTaskStatus s, TaskStatusRecord *r) {
      |                                                      ^
/Users/ktoso/code/swift-project/swift/stdlib/public/Concurrency/TaskPrivate.h:58:7: note: forward declaration of 'swift::ActiveTaskStatus'
   58 | class ActiveTaskStatus;
      |       ^

I'll leave as is I think but thank you very much for review!

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Comment on lines +44 to +46
// 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
Copy link

Choose a reason for hiding this comment

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

I just accidentally dropped by here and noticed that the comment seems clipped and is missing the second case. Probably not worth bothering, but wanted to bring it up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

onCancel callback of the withTaskCancellationHandler() is called more than once.

3 participants