Skip to content

Commit eda5dc2

Browse files
committed
[Concurrency] Adopt noniso(nonsend) in many concurrency lib funcs
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)
1 parent bee1ed3 commit eda5dc2

17 files changed

+1137
-94
lines changed

demangle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
_$sScG4nextxSgyYaF
2+
_$sScG4nextxSgyYaFTu

stdlib/public/Concurrency/CheckedContinuation.swift

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,31 @@ extension CheckedContinuation {
290290
/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)`
291291
/// - SeeAlso: `withUnsafeContinuation(function:_:)`
292292
/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)`
293+
@available(SwiftStdlib 5.1, *)
294+
@_alwaysEmitIntoClient
295+
@_silgen_name("$ss25withCheckedContinuation_X8function_xSS_yScCyxs5NeverOGXEtYalF") // The `_X` suffix is only here to avoid silgen_name clash with original _unsafeInheritExecutor ABI
296+
public nonisolated(nonsending) func withCheckedContinuation<T>(
297+
function: String = #function,
298+
_ body: (CheckedContinuation<T, Never>) -> Void
299+
) async -> sending T {
300+
return await Builtin.withUnsafeContinuation {
301+
let unsafeContinuation = unsafe UnsafeContinuation<T, Never>($0)
302+
return body(unsafe CheckedContinuation(continuation: unsafeContinuation,
303+
function: function))
304+
}
305+
}
306+
293307
@inlinable
294308
@available(SwiftStdlib 5.1, *)
295309
#if !$Embedded
296310
@backDeployed(before: SwiftStdlib 6.0)
297311
#endif
298-
public func withCheckedContinuation<T>(
312+
@abi(func withCheckedContinuation<T>(
313+
isolation: isolated (any Actor)?,
314+
function: String,
315+
_ body: (CheckedContinuation<T, Never>) -> Void
316+
) async -> sending T)
317+
public func _isolatedParam_withCheckedContinuation<T>(
299318
isolation: isolated (any Actor)? = #isolation,
300319
function: String = #function,
301320
_ body: (CheckedContinuation<T, Never>) -> Void
@@ -325,7 +344,6 @@ public func _unsafeInheritExecutor_withCheckedContinuation<T>(
325344
}
326345
}
327346

328-
329347
/// Invokes the passed in closure with a checked continuation for the current task.
330348
///
331349
/// The body of the closure executes synchronously on the calling task, and once it returns
@@ -354,12 +372,36 @@ public func _unsafeInheritExecutor_withCheckedContinuation<T>(
354372
/// - SeeAlso: `withCheckedContinuation(function:_:)`
355373
/// - SeeAlso: `withUnsafeContinuation(function:_:)`
356374
/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)`
375+
@available(SwiftStdlib 5.1, *)
376+
@_alwaysEmitIntoClient
377+
@_silgen_name("$ss33withCheckedThrowingContinuation_X8function_xSS_yScCyxs5Error_pGXEtYaKlF") // The `_X` suffix is only here to avoid silgen_name clash with original _unsafeInheritExecutor ABI
378+
public nonisolated(nonsending) func withCheckedThrowingContinuation<T>(
379+
function: String = #function,
380+
_ body: (CheckedContinuation<T, Error>) -> Void
381+
) async throws -> sending T {
382+
// ABI NOTE: Interestingly enough the 'nonisolated(nonsending)' version of this func has the same ABI
383+
// as the initial implementation, that was '@_unsafeInheritExecutor'. So we can remove the "ABI shim"
384+
// as the current correct impl and the previous impl are both one and the same.
385+
//
386+
// We need to keep around the isolated parameter ABI for compatibility though.
387+
return try await Builtin.withUnsafeThrowingContinuation {
388+
let unsafeContinuation = unsafe UnsafeContinuation<T, Error>($0)
389+
return body(unsafe CheckedContinuation(continuation: unsafeContinuation,
390+
function: function))
391+
}
392+
}
393+
357394
@inlinable
358395
@available(SwiftStdlib 5.1, *)
359396
#if !$Embedded
360397
@backDeployed(before: SwiftStdlib 6.0)
361398
#endif
362-
public func withCheckedThrowingContinuation<T>(
399+
@abi(func withCheckedThrowingContinuation<T>(
400+
isolation: isolated (any Actor)?,
401+
function: String,
402+
_ body: (CheckedContinuation<T, Error>) -> Void
403+
) async throws -> sending T)
404+
func _isolatedParam_withCheckedThrowingContinuation<T>(
363405
isolation: isolated (any Actor)? = #isolation,
364406
function: String = #function,
365407
_ body: (CheckedContinuation<T, Error>) -> Void

stdlib/public/Concurrency/DiscardingTaskGroup.swift

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,38 @@ import Swift
6969
///
7070
/// - SeeAlso: ``TaskGroup``
7171
/// - SeeAlso: ``withThrowingDiscardingTaskGroup(returning:body:)``
72+
@available(SwiftStdlib 5.9, *)
73+
@_alwaysEmitIntoClient
74+
public nonisolated(nonsending) func withDiscardingTaskGroup<GroupResult>(
75+
returning returnType: GroupResult.Type = GroupResult.self,
76+
body: nonisolated(nonsending) (inout DiscardingTaskGroup) async -> GroupResult
77+
) async -> GroupResult {
78+
let flags = taskGroupCreateFlags(
79+
discardResults: true
80+
)
81+
82+
let _group = Builtin.createTaskGroupWithFlags(flags, Void.self)
83+
var group = DiscardingTaskGroup(group: _group)
84+
defer { Builtin.destroyTaskGroup(_group) }
85+
86+
let result = await body(&group)
87+
88+
try! await group.awaitAllRemainingTasksNonsending() // try!-safe, cannot throw since this is a non throwing group
89+
90+
return result
91+
}
92+
7293
@available(SwiftStdlib 5.9, *)
7394
#if !hasFeature(Embedded)
7495
@backDeployed(before: SwiftStdlib 6.0)
7596
#endif
7697
@inlinable
77-
public func withDiscardingTaskGroup<GroupResult>(
98+
@abi(func withDiscardingTaskGroup<GroupResult>(
99+
returning returnType: GroupResult.Type,
100+
isolation: isolated (any Actor)?,
101+
body: (inout DiscardingTaskGroup) async -> GroupResult
102+
) async -> GroupResult)
103+
func _isolatedParameter_withDiscardingTaskGroup<GroupResult>(
78104
returning returnType: GroupResult.Type = GroupResult.self,
79105
isolation: isolated (any Actor)? = #isolation,
80106
body: (inout DiscardingTaskGroup) async -> GroupResult
@@ -182,6 +208,11 @@ public struct DiscardingTaskGroup {
182208
/// Await all the remaining tasks on this group.
183209
///
184210
/// - Throws: The first error that was encountered by this group.
211+
@_alwaysEmitIntoClient
212+
internal nonisolated(nonsending) mutating func awaitAllRemainingTasksNonsending() async throws {
213+
let _: Void? = try await _taskGroupWaitAll(group: _group, bodyError: nil)
214+
}
215+
185216
@usableFromInline
186217
internal mutating func awaitAllRemainingTasks() async throws {
187218
let _: Void? = try await _taskGroupWaitAll(group: _group, bodyError: nil)
@@ -336,15 +367,50 @@ extension DiscardingTaskGroup: Sendable { }
336367
/// }
337368
/// }
338369
/// ```
370+
@available(SwiftStdlib 5.9, *)
371+
@_alwaysEmitIntoClient
372+
public nonisolated(nonsending) func withThrowingDiscardingTaskGroup<GroupResult>(
373+
returning returnType: GroupResult.Type = GroupResult.self,
374+
body: nonisolated(nonsending) (inout ThrowingDiscardingTaskGroup<Error>) async throws -> GroupResult
375+
) async throws -> GroupResult {
376+
let flags = taskGroupCreateFlags(
377+
discardResults: true
378+
)
379+
380+
let _group = Builtin.createTaskGroupWithFlags(flags, Void.self)
381+
var group = ThrowingDiscardingTaskGroup<Error>(group: _group)
382+
defer { Builtin.destroyTaskGroup(_group) }
383+
384+
let result: GroupResult
385+
do {
386+
result = try await body(&group)
387+
} catch {
388+
group.cancelAll()
389+
390+
try await group.awaitAllRemainingTasksNonisolated(bodyError: error)
391+
392+
throw error
393+
}
394+
395+
try await group.awaitAllRemainingTasksNonisolated(bodyError: nil)
396+
397+
return result
398+
}
399+
339400
@available(SwiftStdlib 5.9, *)
340401
#if !hasFeature(Embedded)
341402
@backDeployed(before: SwiftStdlib 6.0)
342403
#endif
343404
@inlinable
344-
public func withThrowingDiscardingTaskGroup<GroupResult>(
345-
returning returnType: GroupResult.Type = GroupResult.self,
346-
isolation: isolated (any Actor)? = #isolation,
347-
body: (inout ThrowingDiscardingTaskGroup<Error>) async throws -> GroupResult
405+
@abi(func withThrowingDiscardingTaskGroup<GroupResult>(
406+
returning returnType: GroupResult.Type,
407+
isolation: isolated (any Actor)?,
408+
body: (inout ThrowingDiscardingTaskGroup<Error>) async throws -> GroupResult
409+
) async throws -> GroupResult)
410+
public func _isolatedParam_withThrowingDiscardingTaskGroup<GroupResult>(
411+
returning returnType: GroupResult.Type = GroupResult.self,
412+
isolation: isolated (any Actor)? = #isolation,
413+
body: (inout ThrowingDiscardingTaskGroup<Error>) async throws -> GroupResult
348414
) async throws -> GroupResult {
349415
let flags = taskGroupCreateFlags(
350416
discardResults: true
@@ -474,6 +540,11 @@ public struct ThrowingDiscardingTaskGroup<Failure: Error> {
474540
}
475541

476542
/// Await all the remaining tasks on this group.
543+
@_alwaysEmitIntoClient
544+
internal nonisolated(nonsending) mutating func awaitAllRemainingTasksNonisolated(bodyError: Error?) async throws {
545+
let _: Void? = try await _taskGroupWaitAll(group: _group, bodyError: bodyError)
546+
}
547+
477548
@usableFromInline
478549
internal mutating func awaitAllRemainingTasks(bodyError: Error?) async throws {
479550
let _: Void? = try await _taskGroupWaitAll(group: _group, bodyError: bodyError)

stdlib/public/Concurrency/PartialAsyncTask.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -889,8 +889,7 @@ internal func _resumeUnsafeThrowingContinuationWithError<T>(
889889
@available(SwiftStdlib 5.1, *)
890890
@_alwaysEmitIntoClient
891891
@unsafe
892-
public func withUnsafeContinuation<T>(
893-
isolation: isolated (any Actor)? = #isolation,
892+
public nonisolated(nonsending) func withUnsafeContinuation<T>(
894893
_ fn: (UnsafeContinuation<T, Never>) -> Void
895894
) async -> sending T {
896895
return await Builtin.withUnsafeContinuation {
@@ -926,8 +925,7 @@ public func withUnsafeContinuation<T>(
926925
@available(SwiftStdlib 5.1, *)
927926
@_alwaysEmitIntoClient
928927
@unsafe
929-
public func withUnsafeThrowingContinuation<T>(
930-
isolation: isolated (any Actor)? = #isolation,
928+
public nonisolated(nonsending) func withUnsafeThrowingContinuation<T>(
931929
_ fn: (UnsafeContinuation<T, Error>) -> Void
932930
) async throws -> sending T {
933931
return try await Builtin.withUnsafeThrowingContinuation {

stdlib/public/Concurrency/Task+TaskExecutor.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,18 +208,22 @@ extension UnsafeCurrentTask {
208208
// ==== Runtime ---------------------------------------------------------------
209209

210210
@available(SwiftStdlib 6.0, *)
211+
@usableFromInline
211212
@_silgen_name("swift_task_getPreferredTaskExecutor")
212213
internal func _getPreferredUnownedTaskExecutor() -> Builtin.Executor
213214

215+
@usableFromInline
214216
typealias TaskExecutorPreferenceStatusRecord = UnsafeRawPointer
215217

216218
@available(SwiftStdlib 6.0, *)
219+
@usableFromInline
217220
@_silgen_name("swift_task_pushTaskExecutorPreference")
218221
internal func _pushTaskExecutorPreference(_ executor: Builtin.Executor)
219222
-> TaskExecutorPreferenceStatusRecord
220223

221224
@available(SwiftStdlib 6.0, *)
222225
@_silgen_name("swift_task_popTaskExecutorPreference")
226+
@usableFromInline
223227
internal func _popTaskExecutorPreference(
224228
record: TaskExecutorPreferenceStatusRecord
225229
)

stdlib/public/Concurrency/TaskCancellation.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,25 @@ import Swift
7474
/// Therefore, if a cancellation handler must acquire a lock, other code should
7575
/// not cancel tasks or resume continuations while holding that lock.
7676
@available(SwiftStdlib 5.1, *)
77-
#if !$Embedded
78-
@backDeployed(before: SwiftStdlib 6.0)
79-
#endif
80-
public func withTaskCancellationHandler<T>(
77+
@_alwaysEmitIntoClient
78+
public nonisolated(nonsending) func withTaskCancellationHandler<T>(
79+
operation: nonisolated(nonsending) () async throws -> T,
80+
onCancel handler: @Sendable () -> Void
81+
) async rethrows -> T {
82+
// unconditionally add the cancellation record to the task.
83+
// if the task was already cancelled, it will be executed right away.
84+
let record = unsafe _taskAddCancellationHandler(handler: handler)
85+
defer { unsafe _taskRemoveCancellationHandler(record: record) }
86+
87+
return try await operation()
88+
}
89+
90+
// Note: Deprecated version which would still hop if we did not close over an `isolated` parameter
91+
// with the operation closure. Instead, we should do what the docs of this method promise - and not hop at all,
92+
// by using the new nonisolated(nonsending)
93+
@available(SwiftStdlib 5.1, *)
94+
@_silgen_name("$ss27withTaskCancellationHandler9operation8onCancel9isolationxxyYaKXE_yyYbXEScA_pSgYitYaKlF")
95+
public func _isolatedParam_withTaskCancellationHandler<T>(
8196
operation: () async throws -> T,
8297
onCancel handler: @Sendable () -> Void,
8398
isolation: isolated (any Actor)? = #isolation

0 commit comments

Comments
 (0)