Skip to content

Commit 13b4456

Browse files
authored
Merge pull request #73839 from ktoso/pick-6-pick-concurrency-isCurrent-more-fixes
2 parents ea51b29 + 135213e commit 13b4456

16 files changed

+523
-116
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 122 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ bool _task_serialExecutor_isSameExclusiveExecutionContext(
323323
enum 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
};
334334
static 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

370391
SWIFT_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

stdlib/public/runtime/Bincompat.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,19 @@ bool useLegacySwiftObjCHashing() {
255255
#endif
256256
}
257257

258+
// Controls legacy mode for the 'swift_task_isCurrentExecutorImpl' runtime function.
259+
//
260+
// In "legacy" / "no crash" mode:
261+
// * The `swift_task_isCurrentExecutorImpl` cannot crash
262+
// * This means cases where no "current" executor is present cannot be diagnosed correctly
263+
// * The runtime can NOT use 'SerialExecutor/checkIsolated'
264+
// * The runtime can NOT use 'dispatch_precondition' which is able ot handle some dispatch and main actor edge cases
265+
//
266+
// New behavior in "swift6" "crash" mode:
267+
// * The 'swift_task_isCurrentExecutorImpl' will CRASH rather than return 'false'
268+
// * This allows the method to invoke 'SerialExecutor/checkIsolated'
269+
// * Which is allowed to call 'dispatch_precondition' and handle "on dispatch queue but not on Swift executor" cases
270+
//
258271
// FIXME(concurrency): Once the release is announced, adjust the logic detecting the SDKs
259272
bool swift_bincompat_useLegacyNonCrashingExecutorChecks() {
260273
#if BINARY_COMPATIBILITY_APPLE

stdlib/public/runtime/EnvironmentVariables.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ VARIABLE(SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE, string, "",
128128
"non-crashing behavior. This flag enables temporarily restoring the "
129129
"legacy 'nocrash' behavior until adopting code has been adjusted. "
130130
"Legal values are: "
131-
" 'nocrash' (Legacy behavior), "
132-
" 'crash' (Swift 6.0+ behavior)")
131+
" 'legacy' (Legacy behavior), "
132+
" 'swift6' (Swift 6.0+ behavior)")
133133

134134
#undef VARIABLE

test/Concurrency/Runtime/actor_assert_precondition_executor.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %target-build-swift -Xfrontend -disable-availability-checking -parse-as-library %s -o %t/a.out
33
// RUN: %target-codesign %t/a.out
4-
// RUN: %target-run %t/a.out
4+
// RUN: %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=legacy %target-run %t/a.out
55

66
// REQUIRES: executable_test
77
// REQUIRES: concurrency
88
// REQUIRES: concurrency_runtime
99

10+
// TODO: The actual reason is that we do these %env- tricks, which e.g. Windows is confused about
11+
// REQUIRES: libdispatch
12+
1013
// UNSUPPORTED: back_deployment_runtime
1114
// UNSUPPORTED: back_deploy_concurrency
1215
// UNSUPPORTED: use_os_stdlib
1316
// UNSUPPORTED: freestanding
1417

15-
// rdar://119743909 fails in optimze tests.
18+
// rdar://119743909 fails in optimize tests.
1619
// UNSUPPORTED: swift_test_mode_optimize
1720
// UNSUPPORTED: swift_test_mode_optimize_size
1821

test/Concurrency/Runtime/actor_assert_precondition_executor_checkIsolated_bincompat.swift renamed to test/Concurrency/Runtime/actor_assert_precondition_executor_checkIsolated_bincompat_crash_swift_6_mode.swift

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
// RUN: %target-run-simple-swift(-parse-as-library -Xfrontend -disable-availability-checking) | %FileCheck %s
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library %s -o %t/a.out
3+
// RUN: %target-codesign %t/a.out
4+
// RUN: %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6 %target-run %t/a.out
25

36
// REQUIRES: executable_test
47
// REQUIRES: concurrency
@@ -11,6 +14,8 @@
1114
// UNSUPPORTED: use_os_stdlib
1215
// UNSUPPORTED: freestanding
1316

17+
import StdlibUnittest
18+
1419
final class NaiveQueueExecutor: SerialExecutor {
1520
init() {}
1621

@@ -38,32 +43,26 @@ actor ActorOnNaiveQueueExecutor {
3843
self.executor.asUnownedSerialExecutor()
3944
}
4045

46+
// Executes on global pool, but our `checkIsolated` impl pretends
47+
// that it is the same executor by never crashing.
4148
nonisolated func checkPreconditionIsolated() async {
4249
print("Before preconditionIsolated")
4350
self.preconditionIsolated()
4451
print("After preconditionIsolated")
45-
46-
print("Before assumeIsolated")
47-
self.assumeIsolated { iso in
48-
print("Inside assumeIsolated")
49-
}
50-
print("After assumeIsolated")
5152
}
5253
}
5354

5455
@main struct Main {
5556
static func main() async {
56-
if #available(SwiftStdlib 6.0, *) {
57-
let actor = ActorOnNaiveQueueExecutor()
58-
await actor.checkPreconditionIsolated()
59-
// CHECK: Before preconditionIsolated
60-
// CHECK-NEXT: checkIsolated: pretend it is ok!
61-
// CHECK-NEXT: After preconditionIsolated
57+
let tests = TestSuite("AssertPreconditionIsolationTests")
6258

63-
// CHECK-NEXT: Before assumeIsolated
64-
// CHECK-NEXT: checkIsolated: pretend it is ok!
65-
// CHECK-NEXT: Inside assumeIsolated
66-
// CHECK-NEXT: After assumeIsolated
59+
if #available(SwiftStdlib 6.0, *) {
60+
tests.test("[swift6+checkIsolated] Isolation assured by invoking 'checkIsolated'") {
61+
let actor = ActorOnNaiveQueueExecutor()
62+
await actor.checkPreconditionIsolated()
63+
}
6764
}
65+
66+
await runAllTestsAsync()
6867
}
6968
}

0 commit comments

Comments
 (0)