Skip to content

Commit 7d9475b

Browse files
committed
[Concurrency] Implement isIsolatingCurrentContext requirement and mode
1 parent 974767e commit 7d9475b

14 files changed

+323
-29
lines changed

include/swift/Runtime/Concurrency.h

+15-2
Original file line numberDiff line numberDiff line change
@@ -773,8 +773,8 @@ void swift_task_enqueue(Job *job, SerialExecutorRef executor);
773773
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
774774
void swift_task_enqueueGlobal(Job *job);
775775

776-
/// Invoke an executor's `checkIsolated` or otherwise equivalent API,
777-
/// that will crash if the current executor is NOT the passed executor.
776+
/// Invoke an executor's `checkIsolated` implementation;
777+
/// It will crash if the current executor is NOT the passed executor.
778778
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
779779
void swift_task_checkIsolated(SerialExecutorRef executor);
780780

@@ -785,6 +785,15 @@ void swift_task_checkIsolated(SerialExecutorRef executor);
785785
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
786786
bool swift_task_invokeSwiftCheckIsolated(SerialExecutorRef executor);
787787

788+
/// Invoke an executor's `isIsolatingCurrentContext` implementation;
789+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
790+
bool swift_task_isIsolatingCurrentContext(SerialExecutorRef executor);
791+
792+
/// Invoke a Swift executor's `isIsolatingCurrentContext` implementation; returns
793+
/// `true` if it invoked the Swift implementation, `false` otherwise.
794+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
795+
bool swift_task_invokeSwiftIsIsolatingCurrentContext(SerialExecutorRef executor);
796+
788797
/// A count in nanoseconds.
789798
using JobDelay = unsigned long long;
790799

@@ -1037,6 +1046,10 @@ enum swift_task_is_current_executor_flag : uint64_t {
10371046

10381047
/// The routine should assert on failure.
10391048
Assert = 0x8,
1049+
1050+
/// The routine should use 'isIsolatingCurrentContext' function on the
1051+
/// 'expected' executor instead of
1052+
HasIsIsolatingCurrentContext = 0x10,
10401053
};
10411054

10421055
SWIFT_EXPORT_FROM(swift_Concurrency)

include/swift/Runtime/ConcurrencyHooks.def

+5-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ SWIFT_CONCURRENCY_HOOK(void, swift_task_enqueueGlobalWithDeadline,
4444
long long tnsec,
4545
int clock, Job *job);
4646

47-
SWIFT_CONCURRENCY_HOOK(void, swift_task_checkIsolated, SerialExecutorRef executor);
47+
SWIFT_CONCURRENCY_HOOK(void, swift_task_checkIsolated,
48+
SerialExecutorRef executor);
49+
50+
SWIFT_CONCURRENCY_HOOK(bool, swift_task_isIsolatingCurrentContext,
51+
SerialExecutorRef executor);
4852

4953
SWIFT_CONCURRENCY_HOOK(bool, swift_task_isOnExecutor,
5054
HeapObject *executor,

stdlib/public/Concurrency/Actor.cpp

+107-20
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,15 @@ enum IsCurrentExecutorCheckMode : unsigned {
341341

342342
// Shimming call to Swift runtime because Swift Embedded does not have
343343
// these symbols defined.
344-
bool __swift_bincompat_useLegacyNonCrashingExecutorChecks() {
344+
swift_task_is_current_executor_flag
345+
__swift_bincompat_useLegacyNonCrashingExecutorChecks() {
345346
#if !SWIFT_CONCURRENCY_EMBEDDED
346-
return swift::runtime::bincompat::
347-
swift_bincompat_useLegacyNonCrashingExecutorChecks();
348-
#else
349-
return false;
347+
if (swift::runtime::bincompat::
348+
swift_bincompat_useLegacyNonCrashingExecutorChecks()) {
349+
return swift_task_is_current_executor_flag::None;
350+
}
350351
#endif
352+
return swift_task_is_current_executor_flag::Assert;
351353
}
352354

353355
// Shimming call to Swift runtime because Swift Embedded does not have
@@ -364,22 +366,40 @@ const char *__swift_runtime_env_useLegacyNonCrashingExecutorChecks() {
364366

365367
// Done this way because of the interaction with the initial value of
366368
// 'unexpectedExecutorLogLevel'
367-
bool swift_bincompat_useLegacyNonCrashingExecutorChecks() {
368-
bool legacyMode = __swift_bincompat_useLegacyNonCrashingExecutorChecks();
369+
swift_task_is_current_executor_flag swift_bincompat_useLegacyNonCrashingExecutorChecks() {
370+
swift_task_is_current_executor_flag options =
371+
__swift_bincompat_useLegacyNonCrashingExecutorChecks();
369372

370373
// Potentially, override the platform detected mode, primarily used in tests.
371374
if (const char *modeStr =
372375
__swift_runtime_env_useLegacyNonCrashingExecutorChecks()) {
376+
SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = '%s'", modeStr);
373377
if (strcmp(modeStr, "nocrash") == 0 ||
374378
strcmp(modeStr, "legacy") == 0) {
375-
return true;
379+
SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = %s => NONE; options = %d",
380+
modeStr, options);
381+
options = swift_task_is_current_executor_flag(
382+
swift_task_is_current_executor_flag::None);
383+
}
384+
385+
if (strcmp(modeStr, "swift62") == 0) {
386+
options = swift_task_is_current_executor_flag(
387+
options | swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext);
388+
// When we're using the isIsolatingCurrentContext we don't want to use crashing APIs,
389+
// so disable it explicitly.
390+
options = swift_task_is_current_executor_flag(
391+
options & ~swift_task_is_current_executor_flag::Assert);
392+
SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = %s => 6.2; options = %d", modeStr, options);
376393
} else if (strcmp(modeStr, "crash") == 0 ||
377394
strcmp(modeStr, "swift6") == 0) {
378-
return false; // don't use the legacy mode
395+
options = swift_task_is_current_executor_flag(
396+
options | swift_task_is_current_executor_flag::Assert);
397+
SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = %s => 6.9; options = %d", modeStr, options);
379398
} // else, just use the platform detected mode
380399
} // no override, use the default mode
381400

382-
return legacyMode;
401+
SWIFT_TASK_DEBUG_LOG("executor checking: options = %d", options);
402+
return options;
383403
}
384404

385405
// Implemented in Swift to avoid some annoying hard-coding about
@@ -407,6 +427,13 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
407427
swift_task_is_current_executor_flag flags) {
408428
auto options = SwiftTaskIsCurrentExecutorOptions(flags);
409429
auto current = ExecutorTrackingInfo::current();
430+
SWIFT_TASK_DEBUG_LOG("executor checking: current task %p", current);
431+
#ifndef NDEBUG
432+
if (options.contains(swift_task_is_current_executor_flag::Assert))
433+
SWIFT_TASK_DEBUG_LOG("executor checking: active option = Assert (%d)", flags);
434+
if (options.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext))
435+
SWIFT_TASK_DEBUG_LOG("executor checking: active option = HasIsIsolatingCurrentContext (%d)", flags);
436+
#endif
410437

411438
if (!current) {
412439
// We have no current executor, i.e. we are running "outside" of Swift
@@ -416,15 +443,30 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
416443

417444
// Special handling the main executor by detecting the main thread.
418445
if (expectedExecutor.isMainExecutor() && isExecutingOnMainThread()) {
446+
SWIFT_TASK_DEBUG_LOG("executor checking: expected is main executor & current thread is main thread => pass", nullptr);
419447
return true;
420448
}
421449

422450
// We cannot use 'complexEquality' as it requires two executor instances,
423451
// and we do not have a 'current' executor here.
424452

453+
if (options.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext)) {
454+
SWIFT_TASK_DEBUG_LOG("executor checking mode option: HasIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext",
455+
expectedExecutor.getIdentity());
456+
// The executor has the most recent 'isIsolatingCurrentContext' API
457+
// so available so we prefer calling that to 'checkIsolated'.
458+
auto result = swift_task_isIsolatingCurrentContext(expectedExecutor);
459+
460+
SWIFT_TASK_DEBUG_LOG("executor checking mode option: HasIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext => %s",
461+
expectedExecutor.getIdentity(), result ? "pass" : "fail");
462+
return result;
463+
}
464+
425465
// Otherwise, as last resort, let the expected executor check using
426466
// external means, as it may "know" this thread is managed by it etc.
427467
if (options.contains(swift_task_is_current_executor_flag::Assert)) {
468+
SWIFT_TASK_DEBUG_LOG("executor checking mode option: Assert; invoke (%p).expectedExecutor",
469+
expectedExecutor.getIdentity());
428470
swift_task_checkIsolated(expectedExecutor); // will crash if not same context
429471

430472
// checkIsolated did not crash, so we are on the right executor, after all!
@@ -436,16 +478,23 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
436478
}
437479

438480
SerialExecutorRef currentExecutor = current->getActiveExecutor();
481+
SWIFT_TASK_DEBUG_LOG("executor checking: current executor %p %s",
482+
currentExecutor.getIdentity(), currentExecutor.getIdentityDebugName());
439483

440484
// Fast-path: the executor is exactly the same memory address;
441485
// We assume executors do not come-and-go appearing under the same address,
442486
// and treat pointer equality of executors as good enough to assume the executor.
443487
if (currentExecutor == expectedExecutor) {
488+
SWIFT_TASK_DEBUG_LOG("executor checking: current executor %p, equal to expected executor => pass",
489+
currentExecutor.getIdentity());
444490
return true;
445491
}
446492

447493
// Fast-path, specialize the common case of comparing two main executors.
448494
if (currentExecutor.isMainExecutor() && expectedExecutor.isMainExecutor()) {
495+
SWIFT_TASK_DEBUG_LOG("executor checking: current executor %p is main executor, and expected executor (%p) is main executor => pass",
496+
currentExecutor.getIdentity(),
497+
expectedExecutor.getIdentity());
449498
return true;
450499
}
451500

@@ -463,8 +512,16 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
463512
// or confirm we actually are on the main queue; or the custom expected
464513
// executor has a chance to implement a similar queue check.
465514
if (!options.contains(swift_task_is_current_executor_flag::Assert)) {
466-
if ((expectedExecutor.isMainExecutor() && !currentExecutor.isMainExecutor()) ||
467-
(!expectedExecutor.isMainExecutor() && currentExecutor.isMainExecutor())) {
515+
if ((expectedExecutor.isMainExecutor() && !currentExecutor.isMainExecutor())) {
516+
SWIFT_TASK_DEBUG_LOG("executor checking: expected executor %p%s is main executor, and current executor %p%s is NOT => fail",
517+
expectedExecutor.getIdentity(), expectedExecutor.getIdentityDebugName(),
518+
currentExecutor.getIdentity(), currentExecutor.getIdentityDebugName());
519+
return false;
520+
}
521+
if ((!expectedExecutor.isMainExecutor() && currentExecutor.isMainExecutor())) {
522+
SWIFT_TASK_DEBUG_LOG("executor checking: expected executor %p%s is NOT main executor, and current executor %p%s is => fail",
523+
expectedExecutor.getIdentity(), expectedExecutor.getIdentityDebugName(),
524+
currentExecutor.getIdentity(), currentExecutor.getIdentityDebugName());
468525
return false;
469526
}
470527
}
@@ -487,6 +544,8 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
487544
currentExecutor.getSerialExecutorWitnessTable()),
488545
reinterpret_cast<const WitnessTable *>(
489546
expectedExecutor.getSerialExecutorWitnessTable()))) {
547+
SWIFT_TASK_DEBUG_LOG("executor checking: can check isComplexEquality (%p, and %p)",
548+
expectedExecutor.getIdentity(), currentExecutor.getIdentity());
490549

491550
auto isSameExclusiveExecutionContextResult =
492551
_task_serialExecutor_isSameExclusiveExecutionContext(
@@ -498,11 +557,25 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
498557
// it and return; if it was false, we need to give checkIsolated another
499558
// chance to check.
500559
if (isSameExclusiveExecutionContextResult) {
560+
SWIFT_TASK_DEBUG_LOG("executor checking: isComplexEquality (%p, and %p) is true => pass",
561+
expectedExecutor.getIdentity(), currentExecutor.getIdentity());
501562
return true;
502563
} // else, we must give 'checkIsolated' a last chance to verify isolation
503564
}
504565
}
505566

567+
if (options.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext)) {
568+
// We can invoke the 'isIsolatingCurrentContext' function.
569+
SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext",
570+
expectedExecutor.getIdentity());
571+
572+
bool checkResult = swift_task_isIsolatingCurrentContext(expectedExecutor);
573+
574+
SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext => %p",
575+
expectedExecutor.getIdentity(), checkResult ? "pass" : "fail");
576+
return checkResult;
577+
}
578+
506579
// This provides a last-resort check by giving the expected SerialExecutor the
507580
// chance to perform a check using some external knowledge if perhaps we are,
508581
// after all, on this executor, but the Swift concurrency runtime was just not
@@ -524,9 +597,13 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
524597
// synchronous, and will not cause suspensions, as that would require the
525598
// presence of a Task.
526599
if (options.contains(swift_task_is_current_executor_flag::Assert)) {
600+
SWIFT_TASK_DEBUG_LOG("executor checking: call (%p).checkIsolated",
601+
expectedExecutor.getIdentity());
527602
swift_task_checkIsolated(expectedExecutor); // will crash if not same context
528603

529604
// The checkIsolated call did not crash, so we are on the right executor.
605+
SWIFT_TASK_DEBUG_LOG("executor checking: call (%p).checkIsolated passed => pass",
606+
expectedExecutor.getIdentity());
530607
return true;
531608
}
532609

@@ -538,12 +615,23 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
538615

539616
// Check override of executor checking mode.
540617
static void swift_task_setDefaultExecutorCheckingFlags(void *context) {
541-
bool useLegacyMode = swift_bincompat_useLegacyNonCrashingExecutorChecks();
542-
auto checkMode = static_cast<swift_task_is_current_executor_flag *>(context);
543-
if (!useLegacyMode) {
544-
*checkMode = swift_task_is_current_executor_flag(
545-
*checkMode | swift_task_is_current_executor_flag::Assert);
546-
}
618+
SWIFT_TASK_DEBUG_LOG("executor checking: swift_task_setDefaultExecutorCheckingFlags = %d", nullptr);
619+
auto useLegacyMode = swift_bincompat_useLegacyNonCrashingExecutorChecks();
620+
SWIFT_TASK_DEBUG_LOG("executor checking: use legacy mode = %d", useLegacyMode);
621+
auto *options = static_cast<swift_task_is_current_executor_flag *>(context);
622+
SWIFT_TASK_DEBUG_LOG("executor checking: options = %d", *options);
623+
// if (!useLegacyMode) {
624+
// *options = swift_task_is_current_executor_flag(
625+
// *options | swift_task_is_current_executor_flag::Assert);
626+
// }
627+
*options = useLegacyMode;
628+
SWIFT_TASK_DEBUG_LOG("executor checking: resulting options = %d", *options);
629+
SWIFT_TASK_DEBUG_LOG("executor checking: option Assert = %d",
630+
SwiftTaskIsCurrentExecutorOptions(*options).
631+
contains(swift_task_is_current_executor_flag::Assert));
632+
SWIFT_TASK_DEBUG_LOG("executor checking: option HasIsIsolatingCurrentContext = %d",
633+
SwiftTaskIsCurrentExecutorOptions(*options).
634+
contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext));
547635
}
548636

549637
SWIFT_CC(swift)
@@ -2294,8 +2382,7 @@ static void swift_task_switchImpl(SWIFT_ASYNC_CONTEXT AsyncContext *resumeContex
22942382
currentTaskExecutor.getIdentity(),
22952383
currentTaskExecutor.isDefined() ? "" : " (undefined)",
22962384
newTaskExecutor.getIdentity(),
2297-
newTaskExecutor.isDefined() ? "" : " (undefined)",
2298-
trackingInfo->isSynchronousStart() ? "[synchronous start]" : "");
2385+
newTaskExecutor.isDefined() ? "" : " (undefined)");
22992386

23002387
// If the current executor is compatible with running the new executor,
23012388
// we can just immediately continue running with the resume function

stdlib/public/Concurrency/ConcurrencyHooks.cpp

+16-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ swift::swift_task_enqueueGlobalWithDeadline(
100100

101101
SWIFT_CC(swift) static void
102102
swift_task_checkIsolatedOrig(SerialExecutorRef executor) {
103-
swift_task_checkIsolatedImpl(*reinterpret_cast<SwiftExecutorRef *>(&executor));}
103+
swift_task_checkIsolatedImpl(*reinterpret_cast<SwiftExecutorRef *>(&executor));
104+
}
104105

105106
void
106107
swift::swift_task_checkIsolated(SerialExecutorRef executor) {
@@ -110,6 +111,20 @@ swift::swift_task_checkIsolated(SerialExecutorRef executor) {
110111
swift_task_checkIsolatedOrig(executor);
111112
}
112113

114+
SWIFT_CC(swift) static bool
115+
swift_task_isIsolatingCurrentContextOrig(SerialExecutorRef executor) {
116+
return swift_task_isIsolatingCurrentContextImpl(
117+
*reinterpret_cast<SwiftExecutorRef *>(&executor));
118+
}
119+
120+
bool
121+
swift::swift_task_isIsolatingCurrentContext(SerialExecutorRef executor) {
122+
if (SWIFT_UNLIKELY(swift_task_isIsolatingCurrentContext_hook))
123+
return swift_task_isIsolatingCurrentContext_hook(executor, swift_task_isIsolatingCurrentContextOrig);
124+
else
125+
return swift_task_isIsolatingCurrentContextOrig(executor);
126+
}
127+
113128
// Implemented in Swift because we need to obtain the user-defined flags on the executor ref.
114129
//
115130
// We could inline this with effort, though.

stdlib/public/Concurrency/CooperativeGlobalExecutor.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
154154
swift_executor_invokeSwiftCheckIsolated(executor);
155155
}
156156

157+
SWIFT_CC(swift)
158+
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
159+
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
160+
}
161+
157162
/// Insert a job into the cooperative global queue with a delay.
158163
SWIFT_CC(swift)
159164
void swift_task_enqueueGlobalWithDelayImpl(SwiftJobDelay delay,

stdlib/public/Concurrency/DispatchGlobalExecutor.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,12 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
448448
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption");
449449
}
450450

451+
SWIFT_CC(swift)
452+
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
453+
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
454+
}
455+
456+
451457
SWIFT_CC(swift)
452458
SwiftExecutorRef swift_task_getMainExecutorImpl() {
453459
return swift_executor_ordinary(

stdlib/public/Concurrency/Executor.swift

+20
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ public protocol SerialExecutor: Executor {
195195
@available(SwiftStdlib 6.0, *)
196196
func checkIsolated()
197197

198+
@available(SwiftStdlib 6.2, *)
199+
func isIsolatingCurrentContext() -> Bool
200+
198201
}
199202

200203
@available(SwiftStdlib 6.0, *)
@@ -210,6 +213,16 @@ extension SerialExecutor {
210213
}
211214
}
212215

216+
@available(SwiftStdlib 6.2, *)
217+
extension SerialExecutor {
218+
219+
@available(SwiftStdlib 6.2, *)
220+
public func isIsolatingCurrentContext() -> Bool {
221+
self.checkIsolated()
222+
return true
223+
}
224+
}
225+
213226
/// An executor that may be used as preferred executor by a task.
214227
///
215228
/// ### Impact of setting a task executor preference
@@ -467,6 +480,13 @@ internal func _task_serialExecutor_checkIsolated<E>(executor: E)
467480
executor.checkIsolated()
468481
}
469482

483+
@available(SwiftStdlib 6.2, *)
484+
@_silgen_name("_task_serialExecutor_isIsolatingCurrentContext")
485+
internal func _task_serialExecutor_isIsolatingCurrentContext<E>(executor: E) -> Bool
486+
where E: SerialExecutor {
487+
return executor.isIsolatingCurrentContext()
488+
}
489+
470490
/// Obtain the executor ref by calling the executor's `asUnownedSerialExecutor()`.
471491
/// The obtained executor ref will have all the user-defined flags set on the executor.
472492
@available(SwiftStdlib 5.9, *)

0 commit comments

Comments
 (0)