@@ -323,7 +323,7 @@ bool _task_serialExecutor_isSameExclusiveExecutionContext(
323
323
enum IsCurrentExecutorCheckMode: unsigned {
324
324
// / The default mode when an app was compiled against "new" enough SDK.
325
325
// / It allows crashing in isCurrentExecutor, and calls into `checkIsolated`.
326
- Default_UseCheckIsolated_AllowCrash ,
326
+ Swift6_UseCheckIsolated_AllowCrash ,
327
327
// / Legacy mode; Primarily to support old applications which used data race
328
328
// / detector with "warning" mode, which is no longer supported. When such app
329
329
// / is re-compiled against a new SDK, it will see crashes in what was
@@ -332,39 +332,60 @@ enum IsCurrentExecutorCheckMode: unsigned {
332
332
Legacy_NoCheckIsolated_NonCrashing,
333
333
};
334
334
static IsCurrentExecutorCheckMode isCurrentExecutorMode =
335
- Default_UseCheckIsolated_AllowCrash;
336
-
335
+ Swift6_UseCheckIsolated_AllowCrash;
337
336
338
337
// Shimming call to Swift runtime because Swift Embedded does not have
339
- // these symbols defined
340
- bool swift_bincompat_useLegacyNonCrashingExecutorChecks () {
338
+ // these symbols defined.
339
+ bool __swift_bincompat_useLegacyNonCrashingExecutorChecks () {
341
340
#if !SWIFT_CONCURRENCY_EMBEDDED
342
- swift::runtime::bincompat::
341
+ return swift::runtime::bincompat::
343
342
swift_bincompat_useLegacyNonCrashingExecutorChecks ();
344
- #endif
343
+ #else
345
344
return false ;
345
+ #endif
346
346
}
347
347
348
- // Check override of executor checking mode.
349
- static void checkIsCurrentExecutorMode (void *context) {
350
- auto useLegacyMode =
351
- swift_bincompat_useLegacyNonCrashingExecutorChecks ();
348
+ // Shimming call to Swift runtime because Swift Embedded does not have
349
+ // these symbols defined.
350
+ const char *__swift_runtime_env_useLegacyNonCrashingExecutorChecks () {
351
+ // Potentially, override the platform detected mode, primarily used in tests.
352
+ #if SWIFT_STDLIB_HAS_ENVIRON && !SWIFT_CONCURRENCY_EMBEDDED
353
+ return swift::runtime::environment::
354
+ concurrencyIsCurrentExecutorLegacyModeOverride ();
355
+ #else
356
+ return nullptr ;
357
+ #endif
358
+ }
359
+
360
+ #pragma clang diagnostic push
361
+ #pragma ide diagnostic ignored "ConstantConditionsOC"
362
+ // Done this way because of the interaction with the initial value of
363
+ // 'unexpectedExecutorLogLevel'
364
+ bool swift_bincompat_useLegacyNonCrashingExecutorChecks () {
365
+ bool legacyMode = __swift_bincompat_useLegacyNonCrashingExecutorChecks ();
352
366
353
367
// Potentially, override the platform detected mode, primarily used in tests.
354
- #if SWIFT_STDLIB_HAS_ENVIRON
355
368
if (const char *modeStr =
356
- runtime::environment::concurrencyIsCurrentExecutorLegacyModeOverride ()) {
357
- if (modeStr) {
358
- if (strcmp (modeStr, " nocrash" ) == 0 ) {
359
- useLegacyMode = true ;
360
- } else if (strcmp (modeStr, " crash" ) == 0 ) {
361
- useLegacyMode = false ;
362
- } // else, just use the platform detected mode
363
- }
364
- }
365
- #endif // SWIFT_STDLIB_HAS_ENVIRON
369
+ __swift_runtime_env_useLegacyNonCrashingExecutorChecks ()) {
370
+ if (strcmp (modeStr, " nocrash" ) == 0 ||
371
+ strcmp (modeStr, " legacy" ) == 0 ) {
372
+ return true ;
373
+ } else if (strcmp (modeStr, " crash" ) == 0 ||
374
+ strcmp (modeStr, " swift6" ) == 0 ) {
375
+ return false ; // don't use the legacy mode
376
+ } // else, just use the platform detected mode
377
+ } // no override, use the default mode
378
+
379
+ return legacyMode;
380
+ }
381
+ #pragma clang diagnostic pop
382
+
383
+ // Check override of executor checking mode.
384
+ static void checkIsCurrentExecutorMode (void *context) {
385
+ bool useLegacyMode =
386
+ swift_bincompat_useLegacyNonCrashingExecutorChecks ();
366
387
isCurrentExecutorMode = useLegacyMode ? Legacy_NoCheckIsolated_NonCrashing
367
- : Default_UseCheckIsolated_AllowCrash ;
388
+ : Swift6_UseCheckIsolated_AllowCrash ;
368
389
}
369
390
370
391
SWIFT_CC (swift)
@@ -373,6 +394,12 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
373
394
374
395
// To support old applications on apple platforms which assumed this call
375
396
// does not crash, try to use a more compatible mode for those apps.
397
+ //
398
+ // We only allow returning `false` directly from this function when operating
399
+ // in 'Legacy_NoCheckIsolated_NonCrashing' mode. If allowing crashes, we
400
+ // instead must call into 'checkIsolated' or crash directly.
401
+ //
402
+ // Whenever we confirm an executor equality, we can return true, in any mode.
376
403
static swift::once_t checkModeToken;
377
404
swift::once (checkModeToken, checkIsCurrentExecutorMode, nullptr );
378
405
@@ -382,20 +409,19 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
382
409
// the expected executor however, so we need to try a bit harder before
383
410
// we fail.
384
411
385
- // Are we expecting the main executor and are using the main thread?
412
+ // Special handling the main executor by detecting the main thread.
386
413
if (expectedExecutor.isMainExecutor () && isExecutingOnMainThread ()) {
387
- // Due to compatibility with pre-checkIsolated code, we cannot remove
388
- // this special handling. CheckIsolated can handle this if the expected
389
- // executor is the main queue / main executor, however, if we cannot call
390
- // checkIsolated we cannot rely on it to handle this.
391
- // TODO: consider removing this branch when `useCrashingCheckIsolated=true`
392
414
return true ;
393
415
}
394
416
417
+ // We cannot use 'complexEquality' as it requires two executor instances,
418
+ // and we do not have a 'current' executor here.
419
+
395
420
// Otherwise, as last resort, let the expected executor check using
396
421
// external means, as it may "know" this thread is managed by it etc.
397
- if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
398
- swift_task_checkIsolated (expectedExecutor);
422
+ if (isCurrentExecutorMode == Swift6_UseCheckIsolated_AllowCrash) {
423
+ swift_task_checkIsolated (expectedExecutor); // will crash if not same context
424
+
399
425
// checkIsolated did not crash, so we are on the right executor, after all!
400
426
return true ;
401
427
}
@@ -418,46 +444,50 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
418
444
return true ;
419
445
}
420
446
421
- // If the expected executor is "default" then we should have matched
422
- // by pointer equality already with the current executor.
423
- if (expectedExecutor.isDefaultActor ()) {
424
- // If the expected executor is a default actor, it makes no sense to try
425
- // the 'checkIsolated' call, it must be equal to the other actor, or it is
426
- // not the same isolation domain.
427
- swift_Concurrency_fatalError (0 , " Incorrect actor executor assumption" );
428
- return false ;
429
- }
430
-
431
- if (expectedExecutor.isMainExecutor () && !currentExecutor.isMainExecutor ()) {
432
- // TODO: Invoke checkIsolated() on "main" SerialQueue once it implements `checkIsolated`, otherwise messages will be sub-par and hard to address
433
- swift_Concurrency_fatalError (0 , " Incorrect actor executor assumption; Expected MainActor executor" );
434
- return false ;
435
- } else if (!expectedExecutor.isMainExecutor () && currentExecutor.isMainExecutor ()) {
436
- // TODO: Invoke checkIsolated() on "main" SerialQueue once it implements `checkIsolated`, otherwise messages will be sub-par and hard to address
437
- swift_Concurrency_fatalError (0 , " Incorrect actor executor assumption; Expected not-MainActor executor" );
438
- return false ;
439
- }
440
-
441
- if (expectedExecutor.isComplexEquality ()) {
442
- if (!swift_compareWitnessTables (
443
- reinterpret_cast <const WitnessTable*>(currentExecutor.getSerialExecutorWitnessTable ()),
444
- reinterpret_cast <const WitnessTable*>(expectedExecutor.getSerialExecutorWitnessTable ()))) {
445
- // different witness table, we cannot invoke complex equality call
447
+ // Only in legacy mode:
448
+ // We check if the current xor expected executor are the main executor.
449
+ // If so only one of them is, we know that WITHOUT 'checkIsolated' or invoking
450
+ // 'dispatch_assert_queue' we cannot be truly sure the expected/current truly
451
+ // are "on the same queue". There exists no non-crashing API to check this,
452
+ // so we PESSIMISTICALLY return false here.
453
+ //
454
+ // In Swift6 mode:
455
+ // We don't do this naive check, because we'll fall back to
456
+ // `expected.checkIsolated()` which, if it is the main executor, will invoke
457
+ // the crashing 'dispatch_assert_queue(main queue)' which will either crash
458
+ // or confirm we actually are on the main queue; or the custom expected
459
+ // executor has a chance to implement a similar queue check.
460
+ if (isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing) {
461
+ if ((expectedExecutor.isMainExecutor () && !currentExecutor.isMainExecutor ()) ||
462
+ (!expectedExecutor.isMainExecutor () && currentExecutor.isMainExecutor ())) {
446
463
return false ;
447
464
}
465
+ }
448
466
449
- // Avoid passing nulls to Swift for the isSame check:
450
- if (!currentExecutor.getIdentity () || !expectedExecutor.getIdentity ()) {
451
- return false ;
467
+ // Complex equality means that if two executors of the same type have some
468
+ // special logic to check if they are "actually the same".
469
+ if (expectedExecutor.isComplexEquality ()) {
470
+ if (currentExecutor.getIdentity () &&
471
+ expectedExecutor.getIdentity () &&
472
+ swift_compareWitnessTables (
473
+ reinterpret_cast <const WitnessTable *>(
474
+ currentExecutor.getSerialExecutorWitnessTable ()),
475
+ reinterpret_cast <const WitnessTable *>(
476
+ expectedExecutor.getSerialExecutorWitnessTable ()))) {
477
+
478
+ auto isSameExclusiveExecutionContextResult =
479
+ _task_serialExecutor_isSameExclusiveExecutionContext (
480
+ currentExecutor.getIdentity (), expectedExecutor.getIdentity (),
481
+ swift_getObjectType (currentExecutor.getIdentity ()),
482
+ expectedExecutor.getSerialExecutorWitnessTable ());
483
+
484
+ // if the 'isSameExclusiveExecutionContext' returned true we trust
485
+ // it and return; if it was false, we need to give checkIsolated another
486
+ // chance to check.
487
+ if (isSameExclusiveExecutionContextResult) {
488
+ return true ;
489
+ } // else, we must give 'checkIsolated' a last chance to verify isolation
452
490
}
453
-
454
- auto result = _task_serialExecutor_isSameExclusiveExecutionContext (
455
- currentExecutor.getIdentity (),
456
- expectedExecutor.getIdentity (),
457
- swift_getObjectType (currentExecutor.getIdentity ()),
458
- expectedExecutor.getSerialExecutorWitnessTable ());
459
-
460
- return result;
461
491
}
462
492
463
493
// This provides a last-resort check by giving the expected SerialExecutor the
@@ -480,21 +510,22 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
480
510
// Note that this only works because the closure in assumeIsolated is
481
511
// synchronous, and will not cause suspensions, as that would require the
482
512
// presence of a Task.
483
- // compat_invoke_swift_task_checkIsolated(expectedExecutor);
484
- if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
485
- swift_task_checkIsolated (expectedExecutor);
513
+ if (isCurrentExecutorMode == Swift6_UseCheckIsolated_AllowCrash) {
514
+ swift_task_checkIsolated (expectedExecutor); // will crash if not same context
515
+
486
516
// The checkIsolated call did not crash, so we are on the right executor.
487
517
return true ;
488
518
}
489
519
490
- // Using legacy mode, if no explicit executor match worked, we assume `false`
520
+ // In the end, since 'checkIsolated' could not be used, so we must assume
521
+ // that the executors are not the same context.
491
522
assert (isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing);
492
523
return false ;
493
524
}
494
525
495
526
// / Logging level for unexpected executors:
496
- // / 0 - no logging
497
- // / 1 - warn on each instance
527
+ // / 0 - no logging -- will be IGNORED when Swift6 mode of isCurrentExecutor is used
528
+ // / 1 - warn on each instance -- will be IGNORED when Swift6 mode of isCurrentExecutor is used
498
529
// / 2 - fatal error
499
530
// /
500
531
// / NOTE: The default behavior on Apple platforms depends on the SDK version
@@ -512,8 +543,24 @@ static void checkUnexpectedExecutorLogLevel(void *context) {
512
543
return ;
513
544
514
545
long level = strtol (levelStr, nullptr , 0 );
515
- if (level >= 0 && level < 3 )
516
- unexpectedExecutorLogLevel = level;
546
+ if (level >= 0 && level < 3 ) {
547
+ if (swift_bincompat_useLegacyNonCrashingExecutorChecks ()) {
548
+ // legacy mode permits doing nothing or just logging, since the method
549
+ // used to perform the check itself is not going to crash:
550
+ unexpectedExecutorLogLevel = level;
551
+ } else {
552
+ // We are in swift6/crash mode of isCurrentExecutor which means that
553
+ // rather than returning false, that method will always CRASH when an
554
+ // executor mismatch is discovered.
555
+ //
556
+ // Thus, for clarity, we set this mode also to crashing, as runtime should
557
+ // not expect to be able to get any logging or ignoring done. In practice,
558
+ // the crash would happen before logging or "ignoring", but this should
559
+ // help avoid confusing situations like "I thought it should log" when
560
+ // debugging the runtime.
561
+ unexpectedExecutorLogLevel = 2 ;
562
+ }
563
+ }
517
564
#endif // SWIFT_STDLIB_HAS_ENVIRON
518
565
}
519
566
0 commit comments