Skip to content

Conversation

@ktoso
Copy link
Contributor

@ktoso ktoso commented Apr 11, 2025

The problem is that even with the #isolation parameter the non-Sendable
async closure operation still would potentially hop off the caller
isolation. We introduced this change because the plan was the
non-Sendable closure would run on the isolated parameter's isolation,
but that's not actually the case: an "re-enqueue" can still happens in those cases.

Instead, we can use the nonisolated(nonsending) on the function and closure,
in order to guarantee there is no hop between those at all, and
developers can trust that adding this cancellation handler will not
cause any unexpected isolation changes and hops.

The API was always documented to not hop as we execute the operation,
so this brings the correct and expected behavior.

This corrects the following APIs:

  • withCheckedContinuation
  • withUnsafeContinuation
  • withCheckedThrowingContinuation
  • withUnsafeThrowingContinuation
  • withTaskGroup
  • withThrowingTaskGroup
  • withDiscardingTaskGroup
  • withThrowingDiscardingTaskGroup
  • TaskLocal.withValue

as well as task group's next() and related ones...

This gets down the "re-enqueue" count of those methods by far, and makes all these more reliable and predictable.

resolves rdar://140110775
resolves rdar://162192512

@ktoso ktoso requested a review from a team as a code owner April 11, 2025 07:24
@ktoso ktoso force-pushed the wip-improve-withTaskCancellationHandler-with-caller-execution branch 2 times, most recently from de601db to 1303d99 Compare April 11, 2025 07:28
@ktoso
Copy link
Contributor Author

ktoso commented Apr 12, 2025

This is blocked by incorrect behavior of the caller execution on a closure rdar://149107104

@ktoso ktoso added the concurrency Feature: umbrella label for concurrency language features label Apr 17, 2025
@ktoso ktoso force-pushed the wip-improve-withTaskCancellationHandler-with-caller-execution branch 2 times, most recently from a583630 to 9ea3293 Compare April 30, 2025 02:13
@ktoso
Copy link
Contributor Author

ktoso commented Apr 30, 2025

Now blocked on rdar://150017382

@ktoso
Copy link
Contributor Author

ktoso commented May 15, 2025

@swift-ci please smoke test

@gottesmm
Copy link
Contributor

@swift-ci smoke test

1 similar comment
@gottesmm
Copy link
Contributor

gottesmm commented Jul 7, 2025

@swift-ci smoke test

@ktoso
Copy link
Contributor Author

ktoso commented Jul 8, 2025

Good news, this was now unblocked by #82858

@ktoso ktoso force-pushed the wip-improve-withTaskCancellationHandler-with-caller-execution branch 3 times, most recently from 25a963e to be2b2ad Compare July 8, 2025 09:28
@ktoso
Copy link
Contributor Author

ktoso commented Jul 8, 2025

@swift-ci please smoke test

@ktoso
Copy link
Contributor Author

ktoso commented Jul 8, 2025

This is almost unblocked, we need to fix throwing nonisolated nonsending (caller isolated) closures to not lose isolation -- rdar://155313349

And there seems to be a problem with noncopyable types and nonisolated nonsending (caller isolated) closures as well; It shows up in cancellation_handler_only_once.swift:20 as a error: copy of noncopyable typed value. -- rdar://155316655

Giving it a run to see if anything else

@ktoso
Copy link
Contributor Author

ktoso commented Jul 8, 2025

@swift-ci please smoke test

@ktoso
Copy link
Contributor Author

ktoso commented Jul 11, 2025

By now this is unlikely to make it into 6.2.0

@ktoso ktoso force-pushed the wip-improve-withTaskCancellationHandler-with-caller-execution branch from 9f1e063 to 0e5a262 Compare October 14, 2025 09:23
@ktoso ktoso requested a review from phausler as a code owner October 14, 2025 09:23
@ktoso ktoso changed the title [Concurrency] improve cancellation handler to not hop and use caller execution context [Concurrency] Correct enqueue behavior in with... functions by adopting nonisolated(nonsending) Oct 14, 2025
@ktoso
Copy link
Contributor Author

ktoso commented Oct 15, 2025

@swift-ci please smoke test

@ktoso
Copy link
Contributor Author

ktoso commented Oct 15, 2025

Fixing all the ABI tests to follow.

@ktoso ktoso force-pushed the wip-improve-withTaskCancellationHandler-with-caller-execution branch from fcdc60b to 01e0397 Compare October 15, 2025 15:17
@ktoso ktoso force-pushed the wip-improve-withTaskCancellationHandler-with-caller-execution branch from 9a2d287 to 3ceae73 Compare December 15, 2025 07:17
@ktoso
Copy link
Contributor Author

ktoso commented Dec 15, 2025

@swift-ci please smoke test

Copy link
Contributor

@jamieQ jamieQ left a comment

Choose a reason for hiding this comment

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

some further unsolicited feedback/questions for your consideration :)

func globalTestFunc(isolation: isolated (any Actor)? = #isolation) async {
isolation!.assertIsolated("wat in \(#function)!")
await withTaskCancellationHandler {
isolation!.assertIsolated("wat in \(#function)!")
Copy link
Contributor

Choose a reason for hiding this comment

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

might be good to test that this works without a capture of the isolated parameter too, to ensure it's relying on the nonisolated(nonsending)-ness of the closure, and not the isolated capture logic.

@ktoso ktoso force-pushed the wip-improve-withTaskCancellationHandler-with-caller-execution branch 4 times, most recently from aa0fb44 to 13e6b40 Compare December 16, 2025 23:15
@ktoso
Copy link
Contributor Author

ktoso commented Dec 16, 2025

@swift-ci please smoke test

#endif
public func withTaskCancellationHandler<T>(
@export(implementation)
public nonisolated(nonsending) func withTaskCancellationHandler<T>(
Copy link
Contributor

Choose a reason for hiding this comment

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

another case that occurred to me recently that probably should get the same treatment (though probably is quite a bit less commonly used): withTaskPriorityEscalationHandler

@ktoso ktoso force-pushed the wip-improve-withTaskCancellationHandler-with-caller-execution branch from 13e6b40 to 6a221b5 Compare January 14, 2026 01:26
@ktoso
Copy link
Contributor Author

ktoso commented Jan 14, 2026

@swift-ci please smoke test

@ktoso
Copy link
Contributor Author

ktoso commented Jan 14, 2026

Builds timed out heh

@ktoso
Copy link
Contributor Author

ktoso commented Jan 14, 2026

A bunch of real failures to follow up on:

17:00:49 Failed Tests (19):
17:00:49 Swift(macosx-x86_64) :: Concurrency/Reflection/reflect_task.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/actor_counters.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/actor_dynamic_subclass.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_let_fibonacci.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_task_executor_nsobject.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_task_sleep.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_task_sleep_cancel.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_taskgroup_cancellation_race.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_taskgroup_discarding_dontLeak.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_taskgroup_discarding_neverConsumingTasks.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_taskgroup_next_on_completed.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_taskgroup_next_on_pending.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/async_taskgroup_throw_rethrow.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/cancellation_handler_concurrent.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/sleep_executor.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/startImmediately.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/Runtime/startImmediately_order.swift
17:00:49 Swift(macosx-x86_64) :: Concurrency/ShrinkWrap.swift
17:00:49 Swift(macosx-x86_64) :: Sanitizers/tsan/norace-task-group-cancellation.swift

Though at least we're past not compiling into runtime issues now, that's progress.

ktoso added 3 commits January 18, 2026 20:22
This drastically lessens the number of unexpected task enqueues and hops
that these APIs cause, and therefore improves performance and even
correctness in a few cases (like the withContinuation APIs)
@ktoso ktoso force-pushed the wip-improve-withTaskCancellationHandler-with-caller-execution branch from 76e13aa to 72ac89e Compare January 18, 2026 11:23
@ktoso
Copy link
Contributor Author

ktoso commented Jan 18, 2026

rebased

@ktoso
Copy link
Contributor Author

ktoso commented Jan 18, 2026

@swift-ci please smoke test

@xedin
Copy link
Contributor

xedin commented Jan 18, 2026

@ktoso there is a linux failure about attributes:

 /home/build-user/swift/stdlib/public/Concurrency/TaskCancellation.swift:99:2: error: cannot use '@_silgen_name' and '@abi' on the same global function because they serve the same purpose

@ktoso
Copy link
Contributor Author

ktoso commented Jan 20, 2026

@swift-ci please smoke test

@ktoso
Copy link
Contributor Author

ktoso commented Jan 20, 2026

Need to figure out how to resolve this in swift-testing:

15:59:08  /home/build-user/swift-testing/Sources/Testing/Running/Runner.RuntimeState.swift:211:22: error: sending 'body' risks causing data races [#SendingRisksDataRace]
15:59:08  209 |     runtimeState.testCase = nil
15:59:08  210 |     return try await Runner.RuntimeState.$current.withValue(runtimeState) {
15:59:08  211 |       try await test.withCancellationHandling(body)
15:59:08      |                      |- error: sending 'body' risks causing data races [#SendingRisksDataRace]
15:59:08      |                      `- note: sending nonisolated(nonsending) task-isolated 'body' to nonisolated instance method 'withCancellationHandling' risks causing data races between nonisolated and nonisolated(nonsending) task-isolated uses
15:59:08  212 |     }
15:59:08  213 |   }
15:59:08  
15:59:08  /home/build-user/swift-testing/Sources/Testing/Running/Runner.RuntimeState.swift:246:26: error: sending 'body' risks causing data races [#SendingRisksDataRace]
15:59:08  244 |     runtimeState.testCase = testCase
15:59:08  245 |     return try await Runner.RuntimeState.$current.withValue(runtimeState) {
15:59:08  246 |       try await testCase.withCancellationHandling(body)
15:59:08      |                          |- error: sending 'body' risks causing data races [#SendingRisksDataRace]
15:59:08      |                          `- note: sending nonisolated(nonsending) task-isolated 'body' to nonisolated instance method 'withCancellationHandling' risks causing data races between nonisolated and nonisolated(nonsending) task-isolated uses
15:59:08  247 |     }
15:59:08  248 |   }
15:59:08  
15:59:08  /home/build-user/swift-testing/Sources/Testing/Test+Discovery.swift:93:36: error: sending 'taskGroup' risks causing data races [#SendingRisksDataRace]
15:59:08   91 |             }
15:59:08   92 |           }
15:59:08   93 |           result = await taskGroup.reduce(into: result) { $0.insert($1) }
15:59:08      |                                    |- error: sending 'taskGroup' risks causing data races [#SendingRisksDataRace]
15:59:08      |                                    `- note: sending nonisolated(nonsending) task-isolated 'taskGroup' to nonisolated instance method 'reduce(into:_:)' risks causing data races between nonisolated and nonisolated(nonsending) task-isolated uses
15:59:08   94 |         }
15:59:08   95 |       }
15:59:08  
15:59:08  /home/build-user/swift-testing/Sources/Testing/Test+Discovery.swift:107:36: error: sending 'taskGroup' risks causing data races [#SendingRisksDataRace]
15:59:08  105 |             }
15:59:08  106 |           }
15:59:08  107 |           result = await taskGroup.reduce(into: result) { $0.insert($1) }
15:59:08      |                                    |- error: sending 'taskGroup' risks causing data races [#SendingRisksDataRace]
15:59:08      |                                    `- note: sending nonisolated(nonsending) task-isolated 'taskGroup' to nonisolated instance method 'reduce(into:_:)' risks causing data races between nonisolated and nonisolated(nonsending) task-isolated uses
15:59:08  108 |         }
15:59:08  109 |       }

@ktoso
Copy link
Contributor Author

ktoso commented Jan 21, 2026

swiftlang/swift-testing#1493
@swift-ci please smoke test

@ktoso
Copy link
Contributor Author

ktoso commented Jan 21, 2026

More new compat issues uncovered


16:58:20  /home/build-user/swift-tools-protocols/Sources/SKLogging/LoggingScope.swift:92:71: error: cannot convert '@concurrent @Sendable () async throws -> Result' to 'nonisolated(nonsending) () async throws -> Result' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol
16:58:20   90 | ) async rethrows -> Result {
16:58:20   91 |   return try await LoggingScope.$_subsystem.withValue(subsystem) {
16:58:20   92 |     return try await LoggingScope.$_scope.withValue(scope, operation: operation)
16:58:20      |                                                                       |- error: cannot convert '@concurrent @Sendable () async throws -> Result' to 'nonisolated(nonsending) () async throws -> Result' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol
16:58:20      |                                                                       `- note: type 'Result' does not conform to 'Sendable' protocol
16:58:20   93 |   }
16:58:20   94 | }

@ktoso
Copy link
Contributor Author

ktoso commented Jan 21, 2026

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

Labels

concurrency Feature: umbrella label for concurrency language features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants