@@ -100,6 +100,43 @@ public protocol SerialExecutor: Executor {
100
100
/// perspective–to execute code assuming one on the other.
101
101
@available ( SwiftStdlib 5 . 9 , * )
102
102
func isSameExclusiveExecutionContext( other: Self ) -> Bool
103
+
104
+ /// Last resort "fallback" isolation check, called when the concurrency runtime
105
+ /// is comparing executors e.g. during ``assumeIsolated()`` and is unable to prove
106
+ /// serial equivalence between the expected (this object), and the current executor.
107
+ ///
108
+ /// During executor comparison, the Swift concurrency runtime attempts to compare
109
+ /// current and expected executors in a few ways (including "complex" equality
110
+ /// between executors (see ``isSameExclusiveExecutionContext(other:)``), and if all
111
+ /// those checks fail, this method is invoked on the expected executor.
112
+ ///
113
+ /// This method MUST crash if it is unable to prove that the current execution
114
+ /// context belongs to this executor. At this point usual executor comparison would
115
+ /// have already failed, though the executor may have some external tracking of
116
+ /// threads it owns, and may be able to prove isolation nevertheless.
117
+ ///
118
+ /// A default implementation is provided that unconditionally crashes the
119
+ /// program, and prevents calling code from proceeding with potentially
120
+ /// not thread-safe execution.
121
+ ///
122
+ /// - Warning: This method must crash and halt program execution if unable
123
+ /// to prove the isolation of the calling context.
124
+ @available ( SwiftStdlib 6 . 0 , * )
125
+ func checkIsolated( )
126
+
127
+ }
128
+
129
+ @available ( SwiftStdlib 6 . 0 , * )
130
+ extension SerialExecutor {
131
+
132
+ @available ( SwiftStdlib 6 . 0 , * )
133
+ public func checkIsolated( ) {
134
+ #if !$Embedded
135
+ fatalError ( " Unexpected isolation context, expected to be executing on \( Self . self) " )
136
+ #else
137
+ Builtin . int_trap ( )
138
+ #endif
139
+ }
103
140
}
104
141
105
142
/// An executor that may be used as preferred executor by a task.
@@ -120,7 +157,7 @@ public protocol SerialExecutor: Executor {
120
157
///
121
158
/// Unstructured tasks do not inherit the task executor.
122
159
@_unavailableInEmbedded
123
- @available ( SwiftStdlib 9999 , * )
160
+ @available ( SwiftStdlib 6 . 0 , * )
124
161
public protocol TaskExecutor : Executor {
125
162
// This requirement is repeated here as a non-override so that we
126
163
// get a redundant witness-table entry for it. This allows us to
@@ -152,7 +189,7 @@ public protocol TaskExecutor: Executor {
152
189
}
153
190
154
191
@_unavailableInEmbedded
155
- @available ( SwiftStdlib 9999 , * )
192
+ @available ( SwiftStdlib 6 . 0 , * )
156
193
extension TaskExecutor {
157
194
public func asUnownedTaskExecutor( ) -> UnownedTaskExecutor {
158
195
UnownedTaskExecutor ( ordinary: self )
@@ -270,7 +307,7 @@ public struct UnownedSerialExecutor: Sendable {
270
307
271
308
272
309
@_unavailableInEmbedded
273
- @available ( SwiftStdlib 9999 , * )
310
+ @available ( SwiftStdlib 6 . 0 , * )
274
311
@frozen
275
312
public struct UnownedTaskExecutor : Sendable {
276
313
#if $BuiltinExecutor
@@ -279,7 +316,7 @@ public struct UnownedTaskExecutor: Sendable {
279
316
280
317
/// SPI: Do not use. Cannot be marked @_spi, since we need to use it from Distributed module
281
318
/// which needs to reach for this from an @_transparent function which prevents @_spi use.
282
- @available ( SwiftStdlib 9999 , * )
319
+ @available ( SwiftStdlib 6 . 0 , * )
283
320
public var _executor : Builtin . Executor {
284
321
self . executor
285
322
}
@@ -303,24 +340,34 @@ public struct UnownedTaskExecutor: Sendable {
303
340
}
304
341
305
342
@_unavailableInEmbedded
306
- @available ( SwiftStdlib 9999 , * )
343
+ @available ( SwiftStdlib 6 . 0 , * )
307
344
extension UnownedTaskExecutor : Equatable {
308
345
@inlinable
309
346
public static func == ( _ lhs: UnownedTaskExecutor , _ rhs: UnownedTaskExecutor ) -> Bool {
310
347
unsafeBitCast ( lhs. executor, to: ( Int, Int) . self) == unsafeBitCast ( rhs. executor, to: ( Int, Int) . self)
311
348
}
312
349
}
313
350
314
- /// Checks if the current task is running on the expected executor.
351
+ /// Returns either `true` or will CRASH if called from a different executor
352
+ /// than the passed `executor`.
353
+ ///
354
+ /// This method will attempt to verify the current executor against `executor`,
355
+ /// and as a last-resort call through to `SerialExecutor.checkIsolated`.
356
+ ///
357
+ /// This method will never return `false`. It either can verify we're on the
358
+ /// correct executor, or will crash the program. It should be used in
359
+ /// isolation correctness guaranteeing APIs.
315
360
///
316
361
/// Generally, Swift programs should be constructed such that it is statically
317
362
/// known that a specific executor is used, for example by using global actors or
318
363
/// custom executors. However, in some APIs it may be useful to provide an
319
364
/// additional runtime check for this, especially when moving towards Swift
320
365
/// concurrency from other runtimes which frequently use such assertions.
366
+ ///
321
367
/// - Parameter executor: The expected executor.
368
+ @_spi ( ConcurrencyExecutors)
322
369
@available ( SwiftStdlib 5 . 9 , * )
323
- @_silgen_name ( " swift_task_isOnExecutor " )
370
+ @_silgen_name ( " swift_task_isOnExecutor " ) // This function will CRASH rather than return `false`!
324
371
public func _taskIsOnExecutor< Executor: SerialExecutor > ( _ executor: Executor ) -> Bool
325
372
326
373
@_spi ( ConcurrencyExecutors)
@@ -361,6 +408,13 @@ internal func _task_serialExecutor_isSameExclusiveExecutionContext<E>(current cu
361
408
currentExecutor. isSameExclusiveExecutionContext ( other: executor)
362
409
}
363
410
411
+ @available ( SwiftStdlib 6 . 0 , * )
412
+ @_silgen_name ( " _task_serialExecutor_checkIsolated " )
413
+ internal func _task_serialExecutor_checkIsolated< E> ( executor: E )
414
+ where E: SerialExecutor {
415
+ executor. checkIsolated ( )
416
+ }
417
+
364
418
/// Obtain the executor ref by calling the executor's `asUnownedSerialExecutor()`.
365
419
/// The obtained executor ref will have all the user-defined flags set on the executor.
366
420
@available ( SwiftStdlib 5 . 9 , * )
@@ -373,7 +427,7 @@ internal func _task_serialExecutor_getExecutorRef<E>(_ executor: E) -> Builtin.E
373
427
/// Obtain the executor ref by calling the executor's `asUnownedTaskExecutor()`.
374
428
/// The obtained executor ref will have all the user-defined flags set on the executor.
375
429
@_unavailableInEmbedded
376
- @available ( SwiftStdlib 9999 , * )
430
+ @available ( SwiftStdlib 6 . 0 , * )
377
431
@_silgen_name ( " _task_executor_getTaskExecutorRef " )
378
432
internal func _task_executor_getTaskExecutorRef( _ taskExecutor: any TaskExecutor ) -> Builtin . Executor {
379
433
return taskExecutor. asUnownedTaskExecutor ( ) . executor
@@ -396,7 +450,7 @@ where E: SerialExecutor {
396
450
}
397
451
398
452
@_unavailableInEmbedded
399
- @available ( SwiftStdlib 9999 , * )
453
+ @available ( SwiftStdlib 6 . 0 , * )
400
454
@_silgen_name ( " _swift_task_enqueueOnTaskExecutor " )
401
455
internal func _enqueueOnTaskExecutor< E> ( job unownedJob: UnownedJob , executor: E ) where E: TaskExecutor {
402
456
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
0 commit comments