Skip to content

Commit 7608717

Browse files
committed
[Concurrency] Implement supressible crash/nocrash mode
1 parent d6b070d commit 7608717

File tree

7 files changed

+103
-17
lines changed

7 files changed

+103
-17
lines changed

include/swift/Runtime/Bincompat.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,33 @@ bool useLegacySwiftObjCHashing();
5151
/// only log a warning. This changes in future releases and this function
5252
/// will fatal error always.
5353
///
54+
/// Similarly, the internal runtime function
55+
/// `swift_task_isCurrentExecutor(expected)` was previously allowed to return
56+
/// `false`. In future releases it will call into `checkIsolated`, and CRASH
57+
/// when previously it would have returned false.
58+
///
59+
/// Because some applications were running with "isolation warnings" and
60+
/// those call into the `isCurrentExecutor` API and expected warnings to be
61+
/// logged, but they ignored those warnings we cannot make them crashing,
62+
/// and must check if the app was built against a new.
63+
///
5464
/// Old behavior:
65+
/// - `swift_task_isCurrentExecutorImpl` cannot crash and does NOT invoke
66+
/// `SerialExecutor.checkIsolated`
67+
/// - `swift_task_isCurrentExecutorImpl` does not invoke `checkIsolated`
5568
/// - logging a warning on concurrency violation is allowed
69+
///
5670
/// New behavior:
5771
/// - always fatal error in `swift_task_reportUnexpectedExecutor`
72+
/// - `swift_task_isCurrentExecutorImpl` will crash when it would have returned
73+
/// false
74+
/// - `swift_task_isCurrentExecutorImpl` does invoke `checkIsolated` when other
75+
/// checks failed
76+
///
77+
/// This can be overridden by using `SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1`
78+
/// or `SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash|nocrash`
5879
SWIFT_RUNTIME_STDLIB_SPI
59-
bool swift_bincompat_useLegacyWarningModeReportUnexpectedExecutor();
80+
bool swift_bincompat_useLegacyNonCrashingExecutorChecks();
6081

6182
} // namespace bincompat
6283

stdlib/public/Concurrency/Actor.cpp

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,54 @@ bool _task_serialExecutor_isSameExclusiveExecutionContext(
316316
const Metadata *selfType,
317317
const SerialExecutorWitnessTable *wtable);
318318

319+
// We currently still support "legacy mode" in which isCurrentExecutor is NOT
320+
// allowed to crash, because it is used to power "log warnings" data race
321+
// detector. This mode is going away in Swift 6, but until then we allow this.
322+
// This override exists primarily to be able to test both code-paths.
323+
enum IsCurrentExecutorCheckMode: unsigned {
324+
/// The default mode when an app was compiled against "new" enough SDK.
325+
/// It allows crashing in isCurrentExecutor, and calls into `checkIsolated`.
326+
Default_UseCheckIsolated_AllowCrash,
327+
/// Legacy mode; Primarily to support old applications which used data race
328+
/// detector with "warning" mode, which is no longer supported. When such app
329+
/// is re-compiled against a new SDK, it will see crashes in what was
330+
/// previously warnings; however, until until recompiled, warnings will be
331+
/// used, and `checkIsolated` cannot be invoked.
332+
Legacy_NoCheckIsolated_NonCrashing,
333+
};
334+
static IsCurrentExecutorCheckMode isCurrentExecutorMode =
335+
Default_UseCheckIsolated_AllowCrash;
336+
337+
// Check override of executor checking mode.
338+
static void checkIsCurrentExecutorMode(void *context) {
339+
auto useLegacyMode =
340+
swift_bincompat_useLegacyNonCrashingExecutorChecks();
341+
342+
// Potentially, override the platform detected mode, primarily used in tests.
343+
#if SWIFT_STDLIB_HAS_ENVIRON
344+
const char *modeStr = getenv("SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE");
345+
if (!modeStr)
346+
return;
347+
348+
if (strcmp(modeStr, "nocrash") == 0) {
349+
useLegacyMode = Legacy_NoCheckIsolated_NonCrashing;
350+
} else if (strcmp(modeStr, "crash") == 0) {
351+
useLegacyMode = Default_UseCheckIsolated_AllowCrash;
352+
} // else, just use the platform detected mode
353+
#endif // SWIFT_STDLIB_HAS_ENVIRON
354+
isCurrentExecutorMode = useLegacyMode ? Legacy_NoCheckIsolated_NonCrashing
355+
: Default_UseCheckIsolated_AllowCrash;
356+
}
357+
319358
SWIFT_CC(swift)
320359
static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor) {
321360
auto current = ExecutorTrackingInfo::current();
322361

362+
// To support old applications on apple platforms which assumed this call
363+
// does not crash, try to use a more compatible mode for those apps.
364+
static swift::once_t checkModeToken;
365+
swift::once(checkModeToken, checkIsCurrentExecutorMode, nullptr);
366+
323367
if (!current) {
324368
// We have no current executor, i.e. we are running "outside" of Swift
325369
// Concurrency. We could still be running on a thread/queue owned by
@@ -328,16 +372,24 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
328372

329373
// Are we expecting the main executor and are using the main thread?
330374
if (expectedExecutor.isMainExecutor() && isExecutingOnMainThread()) {
331-
// TODO(concurrency): consider removing this special case, as checkIsolated will compare against mainQueue already
375+
// Due to compatibility with pre-checkIsolated code, we cannot remove
376+
// this special handling. CheckIsolated can handle this if the expected
377+
// executor is the main queue / main executor, however, if we cannot call
378+
// checkIsolated we cannot rely on it to handle this.
379+
// TODO: consider removing this branch when `useCrashingCheckIsolated=true`
332380
return true;
333381
}
334382

335383
// Otherwise, as last resort, let the expected executor check using
336384
// external means, as it may "know" this thread is managed by it etc.
337-
swift_task_checkIsolated(expectedExecutor);
385+
if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
386+
swift_task_checkIsolated(expectedExecutor);
387+
// checkIsolated did not crash, so we are on the right executor, after all!
388+
return true;
389+
}
338390

339-
// checkIsolated did not crash, so we are on the right executor, after all!
340-
return true;
391+
assert(isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing);
392+
return false;
341393
}
342394

343395
SerialExecutorRef currentExecutor = current->getActiveExecutor();
@@ -416,10 +468,16 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
416468
// Note that this only works because the closure in assumeIsolated is
417469
// synchronous, and will not cause suspensions, as that would require the
418470
// presence of a Task.
419-
swift_task_checkIsolated(expectedExecutor);
471+
// compat_invoke_swift_task_checkIsolated(expectedExecutor);
472+
if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
473+
swift_task_checkIsolated(expectedExecutor);
474+
// The checkIsolated call did not crash, so we are on the right executor.
475+
return true;
476+
}
420477

421-
// The checkIsolated call did not crash, so we are on the right executor.
422-
return true;
478+
// Using legacy mode, if no explicit executor match worked, we assume `false`
479+
assert(isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing);
480+
return false;
423481
}
424482

425483
/// Logging level for unexpected executors:
@@ -431,7 +489,7 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor)
431489
/// an application was linked to. Since Swift 6 the default is to crash,
432490
/// and the logging behavior is no longer available.
433491
static unsigned unexpectedExecutorLogLevel =
434-
swift_bincompat_useLegacyWarningModeReportUnexpectedExecutor()
492+
swift_bincompat_useLegacyNonCrashingExecutorChecks()
435493
? 1 // legacy apps default to the logging mode, and cannot use `checkIsolated`
436494
: 2; // new apps will only crash upon concurrency violations, and will call into `checkIsolated`
437495

stdlib/public/runtime/Bincompat.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ bool useLegacySwiftObjCHashing() {
256256
}
257257

258258
// FIXME(concurrency): Once the release is announced, adjust the logic detecting the SDKs
259-
bool swift_bincompat_useLegacyWarningModeReportUnexpectedExecutor() {
259+
bool swift_bincompat_useLegacyNonCrashingExecutorChecks() {
260260
#if BINARY_COMPATIBILITY_APPLE
261261
return true; // For now, legacy behavior on Apple OSes
262262
#elif SWIFT_TARGET_OS_DARWIN
@@ -266,7 +266,6 @@ bool swift_bincompat_useLegacyWarningModeReportUnexpectedExecutor() {
266266
#endif
267267
}
268268

269-
270269
} // namespace bincompat
271270

272271
} // namespace runtime

test/Concurrency/Runtime/data_race_detection_default.swift renamed to test/Concurrency/Runtime/data_race_detection_crash.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %target-build-swift %import-libdispatch -Xfrontend -disable-availability-checking -enable-actor-data-race-checks -parse-as-library %s -o %t/a.out -module-name main
33
// RUN: %target-codesign %t/a.out
4-
// RUN: env %env-SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=2 %target-run %t/a.out
4+
5+
// NOTE: This test specifically tests the crashing behavior of `checkIsolated`,
6+
// because this behavior is currently disabled
7+
// RUN: env %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash %target-run %t/a.out
58

69
// REQUIRES: executable_test
710
// REQUIRES: concurrency

test/Concurrency/Runtime/data_race_detection_legacy_warning.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %target-build-swift %import-libdispatch -Xfrontend -disable-availability-checking -enable-actor-data-race-checks -parse-as-library %s -o %t/a.out -module-name main
33
// RUN: %target-codesign %t/a.out
4-
// RUN: env %env-SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1 %target-run %t/a.out 2>&1 | %FileCheck %s
4+
5+
// We specifically test for legacy behavior here, apps compiled against old SDKs
6+
// will be able to have this behavior, however new apps will not. We use the
7+
// overrides to test the logic for legacy code remains functional.
8+
//
9+
// RUN: env %env-SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1 %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=nocrash %target-run %t/a.out 2>&1 | %FileCheck %s
510

611
// REQUIRES: executable_test
712
// REQUIRES: concurrency
@@ -61,14 +66,14 @@ actor MyActor {
6166
struct Runner {
6267
static func main() async {
6368
print("Launching a main-actor task")
64-
// CHECK: warning: data race detected: @MainActor function at main/data_race_detection_legacy_warning.swift:25 was not called on the main thread
69+
// CHECK: warning: data race detected: @MainActor function at main/data_race_detection_legacy_warning.swift:30 was not called on the main thread
6570
launchFromMainThread()
6671
sleep(1)
6772

6873
let actor = MyActor()
6974
let actorFn = await actor.getTaskOnMyActor()
7075
print("Launching an actor-instance task")
71-
// CHECK: warning: data race detected: actor-isolated function at main/data_race_detection_legacy_warning.swift:54 was not called on the same actor
76+
// CHECK: warning: data race detected: actor-isolated function at main/data_race_detection_legacy_warning.swift:59 was not called on the same actor
7277
launchTask(actorFn)
7378

7479
sleep(1)

test/abi/macOS/arm64/stdlib.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,4 @@ Added: __swift_willThrowTypedImpl
257257
Added: __swift_enableSwizzlingOfAllocationAndRefCountingFunctions_forInstrumentsOnly
258258

259259
// Runtime bincompat functions for Concurrency runtime to detect legacy mode
260-
Added: _swift_bincompat_useLegacyWarningModeReportUnexpectedExecutor
260+
Added: _swift_bincompat_useLegacyNonCrashingExecutorChecks

test/abi/macOS/x86_64/stdlib.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,4 @@ Added: __swift_willThrowTypedImpl
257257
Added: __swift_enableSwizzlingOfAllocationAndRefCountingFunctions_forInstrumentsOnly
258258

259259
// Runtime bincompat functions for Concurrency runtime to detect legacy mode
260-
Added: _swift_bincompat_useLegacyWarningModeReportUnexpectedExecutor
260+
Added: _swift_bincompat_useLegacyNonCrashingExecutorChecks

0 commit comments

Comments
 (0)