Skip to content

Commit f32a1a2

Browse files
committed
[Concurrency] Further test cleanups, making sure modes interop properly
1 parent e559620 commit f32a1a2

10 files changed

+346
-68
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 103 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -337,33 +337,47 @@ static IsCurrentExecutorCheckMode isCurrentExecutorMode =
337337

338338

339339
// Shimming call to Swift runtime because Swift Embedded does not have
340-
// these symbols defined
340+
// these symbols defined.
341341
bool swift_bincompat_useLegacyNonCrashingExecutorChecks() {
342342
#if !SWIFT_CONCURRENCY_EMBEDDED
343343
return swift::runtime::bincompat::
344344
swift_bincompat_useLegacyNonCrashingExecutorChecks();
345-
#endif
345+
#else
346346
return false;
347+
#endif
347348
}
348349

349350
// Check override of executor checking mode.
350351
static void checkIsCurrentExecutorMode(void *context) {
351352
auto useLegacyMode =
352353
swift_bincompat_useLegacyNonCrashingExecutorChecks();
353354

355+
fprintf(stderr, "[%s:%d](%s) useLegacyMode FROM BINCOMPAT = %s\n", __FILE_NAME__, __LINE__, __FUNCTION__,
356+
useLegacyMode ? "Legacy_NoCheckIsolated_NonCrashing" : "Default_UseCheckIsolated_AllowCrash");
357+
354358
// Potentially, override the platform detected mode, primarily used in tests.
355359
#if SWIFT_STDLIB_HAS_ENVIRON
356-
if (const char *modeStr =
357-
runtime::environment::concurrencyIsCurrentExecutorLegacyModeOverride()) {
358-
if (modeStr) {
359-
if (strcmp(modeStr, "nocrash") == 0) {
360-
useLegacyMode = true;
361-
} else if (strcmp(modeStr, "crash") == 0) {
362-
useLegacyMode = false;
363-
} // else, just use the platform detected mode
364-
}
360+
// if (const char *modeStr = runtime::environment::concurrencyIsCurrentExecutorLegacyModeOverride()) {
361+
if (const char *modeStr = getenv("SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE")) {
362+
if (strcmp(modeStr, "nocrash") == 0 || strcmp(modeStr, "legacy") == 0) {
363+
fprintf(stderr, "[%s:%d](%s) OVERRIDE TO USE [LEGACY] MODE\n", __FILE_NAME__, __LINE__, __FUNCTION__);
364+
useLegacyMode = true;
365+
} else if (strcmp(modeStr, "crash") == 0 || strcmp(modeStr, "swift6") == 0) {
366+
fprintf(stderr, "[%s:%d](%s) OVERRIDE TO USE [CRASH] MODE\n", __FILE_NAME__, __LINE__, __FUNCTION__);
367+
useLegacyMode = false;
368+
} // else, just use the platform detected mode
369+
370+
fprintf(stderr, "[%s:%d](%s) useLegacyMode override from ENV = %s (mode string = %s)\n",
371+
__FILE_NAME__, __LINE__, __FUNCTION__,
372+
useLegacyMode ? "Legacy_NoCheckIsolated_NonCrashing"
373+
: "Default_UseCheckIsolated_AllowCrash",
374+
modeStr);
375+
} else {
376+
fprintf(stderr, "[%s:%d](%s) no override of useLegacyMode...\n",
377+
__FILE_NAME__, __LINE__, __FUNCTION__);
365378
}
366379
#endif // SWIFT_STDLIB_HAS_ENVIRON
380+
367381
isCurrentExecutorMode = useLegacyMode ? Legacy_NoCheckIsolated_NonCrashing
368382
: Default_UseCheckIsolated_AllowCrash;
369383
}
@@ -383,14 +397,27 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
383397
static swift::once_t checkModeToken;
384398
swift::once(checkModeToken, checkIsCurrentExecutorMode, nullptr);
385399

400+
auto modeString = isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing
401+
? "Legacy_NoCheckIsolated_NonCrashing"
402+
: "Default_UseCheckIsolated_AllowCrash";
403+
fprintf(stderr, "[%s:%d](%s) MODE = isCurrentExecutorMode = %s\n", __FILE_NAME__, __LINE__, __FUNCTION__,
404+
modeString);
405+
406+
fprintf(stderr, "[%s:%d](%s) expected executor is main: %d\n", __FILE_NAME__, __LINE__, __FUNCTION__,
407+
expectedExecutor.isMainExecutor());
408+
fprintf(stderr, "[%s:%d](%s) thread is main: %d\n", __FILE_NAME__, __LINE__, __FUNCTION__,
409+
isExecutingOnMainThread());
410+
386411
if (!current) {
412+
fprintf(stderr, "[%s:%d](%s) no current\n", __FILE_NAME__, __LINE__, __FUNCTION__);
387413
// We have no current executor, i.e. we are running "outside" of Swift
388414
// Concurrency. We could still be running on a thread/queue owned by
389415
// the expected executor however, so we need to try a bit harder before
390416
// we fail.
391417

392418
// Are we expecting the main executor and are using the main thread?
393419
if (expectedExecutor.isMainExecutor() && isExecutingOnMainThread()) {
420+
fprintf(stderr, "[%s:%d](%s) main executor; main thread >>>>> true\n", __FILE_NAME__, __LINE__, __FUNCTION__);
394421
// Due to compatibility with pre-checkIsolated code, we cannot remove
395422
// this special handling. CheckIsolated can handle this if the expected
396423
// executor is the main queue / main executor, however, if we cannot call
@@ -399,15 +426,23 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
399426
return true;
400427
}
401428

429+
fprintf(stderr, "[%s:%d](%s) have current executor = %p\n", __FILE_NAME__, __LINE__, __FUNCTION__,
430+
current);
431+
402432
// Otherwise, as last resort, let the expected executor check using
403433
// external means, as it may "know" this thread is managed by it etc.
404434
if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
435+
fprintf(stderr, "[%s:%d](%s) allow crash mode, checkIsolated call \n", __FILE_NAME__, __LINE__, __FUNCTION__);
405436
swift_task_checkIsolated(expectedExecutor);
406437
// checkIsolated did not crash, so we are on the right executor, after all!
438+
fprintf(stderr, "[%s:%d](%s) checkIsolated call passed >>>>> true\n", __FILE_NAME__, __LINE__, __FUNCTION__);
407439
return true;
440+
} else {
441+
fprintf(stderr, "[%s:%d](%s) cannot invoke 'checkIsolated'\n", __FILE_NAME__, __LINE__, __FUNCTION__);
408442
}
409443

410444
assert(isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing);
445+
fprintf(stderr, "[%s:%d](%s) >>>>> false\n", __FILE_NAME__, __LINE__, __FUNCTION__);
411446
return false;
412447
}
413448

@@ -417,39 +452,43 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
417452
// We assume executors do not come-and-go appearing under the same address,
418453
// and treat pointer equality of executors as good enough to assume the executor.
419454
if (currentExecutor == expectedExecutor) {
455+
fprintf(stderr, "[%s:%d](%s) executor pointer equal; >>>>> true\n", __FILE_NAME__, __LINE__, __FUNCTION__);
420456
return true;
421457
}
422458

423459
// Fast-path, specialize the common case of comparing two main executors.
424460
if (currentExecutor.isMainExecutor() && expectedExecutor.isMainExecutor()) {
461+
fprintf(stderr, "[%s:%d](%s) both are main executor >>>>> true\n", __FILE_NAME__, __LINE__, __FUNCTION__);
425462
return true;
426463
}
427464

428-
// If the expected executor is "default" then we should have matched
429-
// by pointer equality already with the current executor.
430-
if (expectedExecutor.isDefaultActor()) {
431-
// If the expected executor is a default actor, it makes no sense to try
432-
// the 'checkIsolated' call, it must be equal to the other actor, or it is
433-
// not the same isolation domain.
434-
if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
435-
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption");
436-
}
437-
return false;
438-
}
439-
440-
if (expectedExecutor.isMainExecutor() && !currentExecutor.isMainExecutor()) {
441-
if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
442-
// TODO: Invoke checkIsolated() on "main" SerialQueue once it implements `checkIsolated`, for potentially better/more consistent error messages
443-
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected MainActor executor");
444-
}
445-
return false;
446-
} else if (!expectedExecutor.isMainExecutor() && currentExecutor.isMainExecutor()) {
447-
if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
448-
// TODO: Invoke checkIsolated() on "main" SerialQueue once it implements `checkIsolated`, for potentially better/more consistent error messages
449-
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected not-MainActor executor");
450-
}
451-
return false;
452-
}
465+
// FIXME: this was incorrect, if expected is default we need to invoke the checkIsolated
466+
// // If the expected executor is "default" then we should have matched
467+
// // by pointer equality already with the current executor.
468+
// if (expectedExecutor.isDefaultActor()) {
469+
// // If the expected executor is a default actor, it makes no sense to try
470+
// // the 'checkIsolated' call, it must be equal to the other actor, or it is
471+
// // not the same isolation domain.
472+
// fprintf(stderr, "[%s:%d](%s) both are main executor >>>>> true\n", __FILE_NAME__, __LINE__, __FUNCTION__);
473+
// if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
474+
// swift_Concurrency_fatalError(0, "Incorrect actor executor assumption");
475+
// }
476+
// return false;
477+
// }
478+
479+
// FIXME: all of this is wrong, we need to keep trying complex equality;
480+
// as something may not be EQUAL to the main executor but still be equivalent to it.
481+
// if (expectedExecutor.isMainExecutor() && !currentExecutor.isMainExecutor()) {
482+
// if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
483+
// swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected MainActor executor");
484+
// }
485+
// return false;
486+
// } else if (!expectedExecutor.isMainExecutor() && currentExecutor.isMainExecutor()) {
487+
// if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
488+
// swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected not-MainActor executor");
489+
// }
490+
// return false;
491+
// }
453492

454493
if (expectedExecutor.isComplexEquality()) {
455494
if (currentExecutor.getIdentity() && expectedExecutor.getIdentity() &&
@@ -469,6 +508,7 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
469508
// it and return; if it was false, we need to give checkIsolated another
470509
// chance to check.
471510
if (isSameExclusiveExecutionContextResult) {
511+
fprintf(stderr, "[%s:%d](%s) is same exclusive execution context! >>>> true\n", __FILE_NAME__, __LINE__, __FUNCTION__);
472512
return true;
473513
}
474514
}
@@ -496,12 +536,15 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
496536
// presence of a Task.
497537
// compat_invoke_swift_task_checkIsolated(expectedExecutor);
498538
if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
539+
fprintf(stderr, "[%s:%d](%s) last chance; invoke 'checkIsolated'\n", __FILE_NAME__, __LINE__, __FUNCTION__);
499540
swift_task_checkIsolated(expectedExecutor);
541+
500542
// The checkIsolated call did not crash, so we are on the right executor.
543+
fprintf(stderr, "[%s:%d](%s) checkIsolated did not crash; >>>>>> true\n", __FILE_NAME__, __LINE__, __FUNCTION__);
501544
return true;
502545
}
503546

504-
assert(isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing);
547+
fprintf(stderr, "[%s:%d](%s) cannot invoke 'checkIsolated' assume not equal >>>>>> false \n", __FILE_NAME__, __LINE__, __FUNCTION__);
505548
return false;
506549
}
507550

@@ -524,9 +567,30 @@ static void checkUnexpectedExecutorLogLevel(void *context) {
524567
if (!levelStr)
525568
return;
526569

570+
auto isSameExecutorLegacyMode =
571+
swift_bincompat_useLegacyNonCrashingExecutorChecks();
572+
527573
long level = strtol(levelStr, nullptr, 0);
528-
if (level >= 0 && level < 3)
529-
unexpectedExecutorLogLevel = level;
574+
if (level >= 0 && level < 3) {
575+
if (isSameExecutorLegacyMode) {
576+
// legacy mode permits doing nothing or just logging, since the method
577+
// used to perform the check itself is not going to crash:
578+
unexpectedExecutorLogLevel = level;
579+
} else {
580+
// We are in swift6/crash mode of isSameExecutor... which means that
581+
// rather than returning false, that method will always CRASH when an
582+
// executor mismatch is discovered. This means that it is not possible
583+
// to support logging or warning 'unexpected executor log level' in this mode.
584+
if (level > 1) {
585+
fprintf(stderr, "[%s:%d](%s) [unexpected executor] ONLY CRASH MODE IS OK\n", __FILE_NAME__, __LINE__, __FUNCTION__);
586+
unexpectedExecutorLogLevel = level;
587+
} else {
588+
fprintf(stderr,
589+
"[%s:%d](%s) [unexpected executor] HAD TO IGNORE LEVEL [%s] CRASH MODE IS OK\n",
590+
level, __FILE_NAME__, __LINE__, __FUNCTION__);
591+
}
592+
}
593+
}
530594
#endif // SWIFT_STDLIB_HAS_ENVIRON
531595
}
532596

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

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
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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=legacy %target-run %t/a.out
5+
6+
// REQUIRES: executable_test
7+
// REQUIRES: concurrency
8+
// REQUIRES: concurrency_runtime
9+
10+
// REQUIRES: libdispatch
11+
12+
// UNSUPPORTED: back_deployment_runtime
13+
// UNSUPPORTED: back_deploy_concurrency
14+
// UNSUPPORTED: use_os_stdlib
15+
// UNSUPPORTED: freestanding
16+
17+
import StdlibUnittest
18+
19+
final class NaiveQueueExecutor: SerialExecutor {
20+
init() {}
21+
22+
public func enqueue(_ job: consuming ExecutorJob) {
23+
job.runSynchronously(on: self.asUnownedSerialExecutor())
24+
}
25+
26+
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
27+
UnownedSerialExecutor(ordinary: self)
28+
}
29+
30+
func checkIsolated() {
31+
print("checkIsolated: pretend it is ok!")
32+
}
33+
}
34+
35+
actor ActorOnNaiveQueueExecutor {
36+
let executor: NaiveQueueExecutor
37+
38+
init() {
39+
self.executor = NaiveQueueExecutor()
40+
}
41+
42+
nonisolated var unownedExecutor: UnownedSerialExecutor {
43+
self.executor.asUnownedSerialExecutor()
44+
}
45+
46+
// Executes on global pool, but our `checkIsolated` impl pretends
47+
// that it is the same executor by never crashing.
48+
nonisolated func checkPreconditionIsolated() async {
49+
print("Before preconditionIsolated")
50+
self.preconditionIsolated()
51+
print("After preconditionIsolated")
52+
}
53+
}
54+
55+
@main struct Main {
56+
static func main() async {
57+
let tests = TestSuite("AssertPreconditionIsolationTests")
58+
59+
if #available(SwiftStdlib 6.0, *) {
60+
tests.test("[legacy mode] expect crash since unable to invoke 'checkIsolated'") {
61+
expectCrashLater() // In legacy mode we do NOT invoke 'checkIsolated' and therefore will crash
62+
63+
let actor = ActorOnNaiveQueueExecutor()
64+
await actor.checkPreconditionIsolated()
65+
}
66+
}
67+
68+
await runAllTestsAsync()
69+
}
70+
}

0 commit comments

Comments
 (0)