@@ -323,7 +323,7 @@ bool _task_serialExecutor_isSameExclusiveExecutionContext(
323323enum IsCurrentExecutorCheckMode: unsigned {
324324 // / The default mode when an app was compiled against "new" enough SDK.
325325 // / It allows crashing in isCurrentExecutor, and calls into `checkIsolated`.
326- Default_UseCheckIsolated_AllowCrash ,
326+ Swift6_UseCheckIsolated_AllowCrash ,
327327 // / Legacy mode; Primarily to support old applications which used data race
328328 // / detector with "warning" mode, which is no longer supported. When such app
329329 // / is re-compiled against a new SDK, it will see crashes in what was
@@ -332,39 +332,60 @@ enum IsCurrentExecutorCheckMode: unsigned {
332332 Legacy_NoCheckIsolated_NonCrashing,
333333};
334334static IsCurrentExecutorCheckMode isCurrentExecutorMode =
335- Default_UseCheckIsolated_AllowCrash;
336-
335+ Swift6_UseCheckIsolated_AllowCrash;
337336
338337// 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 () {
341340#if !SWIFT_CONCURRENCY_EMBEDDED
342- swift::runtime::bincompat::
341+ return swift::runtime::bincompat::
343342 swift_bincompat_useLegacyNonCrashingExecutorChecks ();
344- #endif
343+ #else
345344 return false ;
345+ #endif
346346}
347347
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 ();
352366
353367 // Potentially, override the platform detected mode, primarily used in tests.
354- #if SWIFT_STDLIB_HAS_ENVIRON
355368 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 ();
366387 isCurrentExecutorMode = useLegacyMode ? Legacy_NoCheckIsolated_NonCrashing
367- : Default_UseCheckIsolated_AllowCrash ;
388+ : Swift6_UseCheckIsolated_AllowCrash ;
368389}
369390
370391SWIFT_CC (swift)
@@ -373,6 +394,12 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
373394
374395 // To support old applications on apple platforms which assumed this call
375396 // 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.
376403 static swift::once_t checkModeToken;
377404 swift::once (checkModeToken, checkIsCurrentExecutorMode, nullptr );
378405
@@ -382,20 +409,19 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
382409 // the expected executor however, so we need to try a bit harder before
383410 // we fail.
384411
385- // Are we expecting the main executor and are using the main thread?
412+ // Special handling the main executor by detecting the main thread.
386413 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`
392414 return true ;
393415 }
394416
417+ // We cannot use 'complexEquality' as it requires two executor instances,
418+ // and we do not have a 'current' executor here.
419+
395420 // Otherwise, as last resort, let the expected executor check using
396421 // 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+
399425 // checkIsolated did not crash, so we are on the right executor, after all!
400426 return true ;
401427 }
@@ -418,46 +444,50 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
418444 return true ;
419445 }
420446
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 ())) {
446463 return false ;
447464 }
465+ }
448466
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
452490 }
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;
461491 }
462492
463493 // This provides a last-resort check by giving the expected SerialExecutor the
@@ -480,21 +510,22 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
480510 // Note that this only works because the closure in assumeIsolated is
481511 // synchronous, and will not cause suspensions, as that would require the
482512 // 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+
486516 // The checkIsolated call did not crash, so we are on the right executor.
487517 return true ;
488518 }
489519
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.
491522 assert (isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing);
492523 return false ;
493524}
494525
495526// / 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
498529// / 2 - fatal error
499530// /
500531// / NOTE: The default behavior on Apple platforms depends on the SDK version
@@ -512,8 +543,24 @@ static void checkUnexpectedExecutorLogLevel(void *context) {
512543 return ;
513544
514545 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+ }
517564#endif // SWIFT_STDLIB_HAS_ENVIRON
518565}
519566
0 commit comments