[SerialExecutor] SerialExecutor.checkIsolated() to check its own tracking for isolation checks (#71172)

This commit is contained in:
Konrad `ktoso` Malawski
2024-03-29 07:06:34 +09:00
committed by GitHub
parent 233c1675cf
commit 86f5441294
25 changed files with 831 additions and 47 deletions

View File

@@ -155,8 +155,12 @@ public:
return reinterpret_cast<DefaultActor*>(Identity);
}
bool hasSerialExecutorWitnessTable() const {
return !isGeneric() && !isDefaultActor();
}
const SerialExecutorWitnessTable *getSerialExecutorWitnessTable() const {
assert(!isGeneric() && !isDefaultActor());
assert(hasSerialExecutorWitnessTable());
auto table = Implementation & WitnessTableMask;
return reinterpret_cast<const SerialExecutorWitnessTable*>(table);
}

View File

@@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
@@ -47,6 +47,38 @@ bool useLegacySwiftValueUnboxingInCasting();
/// if present.
bool useLegacySwiftObjCHashing();
/// Legacy semantics allowed for the `swift_task_reportUnexpectedExecutor` to
/// only log a warning. This changes in future releases and this function
/// will fatal error always.
///
/// Similarly, the internal runtime function
/// `swift_task_isCurrentExecutor(expected)` was previously allowed to return
/// `false`. In future releases it will call into `checkIsolated`, and CRASH
/// when previously it would have returned false.
///
/// Because some applications were running with "isolation warnings" and
/// those call into the `isCurrentExecutor` API and expected warnings to be
/// logged, but they ignored those warnings we cannot make them crashing,
/// and must check if the app was built against a new.
///
/// Old behavior:
/// - `swift_task_isCurrentExecutorImpl` cannot crash and does NOT invoke
/// `SerialExecutor.checkIsolated`
/// - `swift_task_isCurrentExecutorImpl` does not invoke `checkIsolated`
/// - logging a warning on concurrency violation is allowed
///
/// New behavior:
/// - always fatal error in `swift_task_reportUnexpectedExecutor`
/// - `swift_task_isCurrentExecutorImpl` will crash when it would have returned
/// false
/// - `swift_task_isCurrentExecutorImpl` does invoke `checkIsolated` when other
/// checks failed
///
/// This can be overridden by using `SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1`
/// or `SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash|nocrash`
SWIFT_RUNTIME_STDLIB_SPI
bool swift_bincompat_useLegacyNonCrashingExecutorChecks();
} // namespace bincompat
} // namespace runtime

View File

@@ -715,6 +715,11 @@ void swift_task_enqueue(Job *job, SerialExecutorRef executor);
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_task_enqueueGlobal(Job *job);
/// Invoke an executor's `checkIsolated` or otherwise equivalent API,
/// that will crash if the current executor is NOT the passed executor.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_task_checkIsolated(SerialExecutorRef executor);
/// A count in nanoseconds.
using JobDelay = unsigned long long;
@@ -729,6 +734,9 @@ void swift_task_enqueueGlobalWithDeadline(long long sec, long long nsec,
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_task_enqueueMainExecutor(Job *job);
/// WARNING: This method is expected to CRASH when caller is not on the
/// expected executor.
///
/// Return true if the caller is running in a Task on the passed Executor.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_task_isOnExecutor(
@@ -781,6 +789,12 @@ SWIFT_CC(swift) void (*swift_task_enqueueGlobalWithDeadline_hook)(
int clock, Job *job,
swift_task_enqueueGlobalWithDeadline_original original);
typedef SWIFT_CC(swift) void (*swift_task_checkIsolated_original)(SerialExecutorRef executor);
SWIFT_EXPORT_FROM(swift_Concurrency)
SWIFT_CC(swift) void (*swift_task_checkIsolated_hook)(
SerialExecutorRef executor, swift_task_checkIsolated_original original);
typedef SWIFT_CC(swift) bool (*swift_task_isOnExecutor_original)(
HeapObject *executor,
const Metadata *selfType,

View File

@@ -22,10 +22,12 @@
#include "../CompatibilityOverride/CompatibilityOverride.h"
#include "swift/ABI/Actor.h"
#include "swift/ABI/Task.h"
#include "TaskPrivate.h"
#include "swift/Basic/ListMerger.h"
#include "swift/Concurrency/Actor.h"
#include "swift/Runtime/AccessibleFunction.h"
#include "swift/Runtime/Atomic.h"
#include "swift/Runtime/Bincompat.h"
#include "swift/Runtime/Casting.h"
#include "swift/Runtime/DispatchShims.h"
#include "swift/Threading/Mutex.h"
@@ -313,39 +315,178 @@ bool _task_serialExecutor_isSameExclusiveExecutionContext(
const Metadata *selfType,
const SerialExecutorWitnessTable *wtable);
// We currently still support "legacy mode" in which isCurrentExecutor is NOT
// allowed to crash, because it is used to power "log warnings" data race
// detector. This mode is going away in Swift 6, but until then we allow this.
// This override exists primarily to be able to test both code-paths.
enum IsCurrentExecutorCheckMode: unsigned {
/// The default mode when an app was compiled against "new" enough SDK.
/// It allows crashing in isCurrentExecutor, and calls into `checkIsolated`.
Default_UseCheckIsolated_AllowCrash,
/// Legacy mode; Primarily to support old applications which used data race
/// detector with "warning" mode, which is no longer supported. When such app
/// is re-compiled against a new SDK, it will see crashes in what was
/// previously warnings; however, until until recompiled, warnings will be
/// used, and `checkIsolated` cannot be invoked.
Legacy_NoCheckIsolated_NonCrashing,
};
static IsCurrentExecutorCheckMode isCurrentExecutorMode =
Default_UseCheckIsolated_AllowCrash;
// Shimming call to Swift runtime because Swift Embedded does not have
// these symbols defined
bool swift_bincompat_useLegacyNonCrashingExecutorChecks() {
#if !SWIFT_CONCURRENCY_EMBEDDED
swift::runtime::bincompat::
swift_bincompat_useLegacyNonCrashingExecutorChecks();
#endif
return false;
}
// Check override of executor checking mode.
static void checkIsCurrentExecutorMode(void *context) {
auto useLegacyMode =
swift_bincompat_useLegacyNonCrashingExecutorChecks();
// Potentially, override the platform detected mode, primarily used in tests.
#if SWIFT_STDLIB_HAS_ENVIRON
const char *modeStr = getenv("SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE");
if (!modeStr)
return;
if (strcmp(modeStr, "nocrash") == 0) {
useLegacyMode = Legacy_NoCheckIsolated_NonCrashing;
} else if (strcmp(modeStr, "crash") == 0) {
useLegacyMode = Default_UseCheckIsolated_AllowCrash;
} // else, just use the platform detected mode
#endif // SWIFT_STDLIB_HAS_ENVIRON
isCurrentExecutorMode = useLegacyMode ? Legacy_NoCheckIsolated_NonCrashing
: Default_UseCheckIsolated_AllowCrash;
}
SWIFT_CC(swift)
static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef executor) {
static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor) {
auto current = ExecutorTrackingInfo::current();
// To support old applications on apple platforms which assumed this call
// does not crash, try to use a more compatible mode for those apps.
static swift::once_t checkModeToken;
swift::once(checkModeToken, checkIsCurrentExecutorMode, nullptr);
if (!current) {
// TODO(ktoso): checking the "is main thread" is not correct, main executor can be not main thread, relates to rdar://106188692
return executor.isMainExecutor() && isExecutingOnMainThread();
// We have no current executor, i.e. we are running "outside" of Swift
// Concurrency. We could still be running on a thread/queue owned by
// the expected executor however, so we need to try a bit harder before
// we fail.
// Are we expecting the main executor and are using the main thread?
if (expectedExecutor.isMainExecutor() && isExecutingOnMainThread()) {
// Due to compatibility with pre-checkIsolated code, we cannot remove
// this special handling. CheckIsolated can handle this if the expected
// executor is the main queue / main executor, however, if we cannot call
// checkIsolated we cannot rely on it to handle this.
// TODO: consider removing this branch when `useCrashingCheckIsolated=true`
return true;
}
// Otherwise, as last resort, let the expected executor check using
// external means, as it may "know" this thread is managed by it etc.
if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
swift_task_checkIsolated(expectedExecutor);
// checkIsolated did not crash, so we are on the right executor, after all!
return true;
}
assert(isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing);
return false;
}
auto currentExecutor = current->getActiveExecutor();
if (currentExecutor == executor) {
SerialExecutorRef currentExecutor = current->getActiveExecutor();
// Fast-path: the executor is exactly the same memory address;
// We assume executors do not come-and-go appearing under the same address,
// and treat pointer equality of executors as good enough to assume the executor.
if (currentExecutor == expectedExecutor) {
return true;
}
if (executor.isComplexEquality()) {
// Fast-path, specialize the common case of comparing two main executors.
if (currentExecutor.isMainExecutor() && expectedExecutor.isMainExecutor()) {
return true;
}
// If the expected executor is "default" then we should have matched
// by pointer equality already with the current executor.
if (expectedExecutor.isDefaultActor()) {
// If the expected executor is a default actor, it makes no sense to try
// the 'checkIsolated' call, it must be equal to the other actor, or it is
// not the same isolation domain.
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption");
return false;
}
if (expectedExecutor.isMainExecutor() && !currentExecutor.isMainExecutor()) {
// TODO: Invoke checkIsolated() on "main" SerialQueue once it implements `checkIsolated`, otherwise messages will be sub-par and hard to address
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected MainActor executor");
return false;
} else if (!expectedExecutor.isMainExecutor() && currentExecutor.isMainExecutor()) {
// TODO: Invoke checkIsolated() on "main" SerialQueue once it implements `checkIsolated`, otherwise messages will be sub-par and hard to address
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected not-MainActor executor");
return false;
}
if (expectedExecutor.isComplexEquality()) {
if (!swift_compareWitnessTables(
reinterpret_cast<const WitnessTable*>(currentExecutor.getSerialExecutorWitnessTable()),
reinterpret_cast<const WitnessTable*>(executor.getSerialExecutorWitnessTable()))) {
reinterpret_cast<const WitnessTable*>(expectedExecutor.getSerialExecutorWitnessTable()))) {
// different witness table, we cannot invoke complex equality call
return false;
}
// Avoid passing nulls to Swift for the isSame check:
if (!currentExecutor.getIdentity() || !executor.getIdentity()) {
if (!currentExecutor.getIdentity() || !expectedExecutor.getIdentity()) {
return false;
}
return _task_serialExecutor_isSameExclusiveExecutionContext(
auto result = _task_serialExecutor_isSameExclusiveExecutionContext(
currentExecutor.getIdentity(),
executor.getIdentity(),
expectedExecutor.getIdentity(),
swift_getObjectType(currentExecutor.getIdentity()),
executor.getSerialExecutorWitnessTable());
expectedExecutor.getSerialExecutorWitnessTable());
return result;
}
// This provides a last-resort check by giving the expected SerialExecutor the
// chance to perform a check using some external knowledge if perhaps we are,
// after all, on this executor, but the Swift concurrency runtime was just not
// aware.
//
// Unless handled in `swift_task_checkIsolated` directly, this should call
// through to the executor's `SerialExecutor.checkIsolated`.
//
// This call is expected to CRASH, unless it has some way of proving that
// we're actually indeed running on this executor.
//
// For example, when running outside of Swift concurrency tasks, but trying to
// `MainActor.assumeIsolated` while executing DIRECTLY on the main dispatch
// queue, this allows Dispatch to check for this using its own tracking
// mechanism, and thus allow the assumeIsolated to work correctly, even though
// the code executing is not even running inside a Task.
//
// Note that this only works because the closure in assumeIsolated is
// synchronous, and will not cause suspensions, as that would require the
// presence of a Task.
// compat_invoke_swift_task_checkIsolated(expectedExecutor);
if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) {
swift_task_checkIsolated(expectedExecutor);
// The checkIsolated call did not crash, so we are on the right executor.
return true;
}
// Using legacy mode, if no explicit executor match worked, we assume `false`
assert(isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing);
return false;
}
@@ -353,7 +494,14 @@ static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef executor) {
/// 0 - no logging
/// 1 - warn on each instance
/// 2 - fatal error
static unsigned unexpectedExecutorLogLevel = 2;
///
/// NOTE: The default behavior on Apple platforms depends on the SDK version
/// an application was linked to. Since Swift 6 the default is to crash,
/// and the logging behavior is no longer available.
static unsigned unexpectedExecutorLogLevel =
swift_bincompat_useLegacyNonCrashingExecutorChecks()
? 1 // legacy apps default to the logging mode, and cannot use `checkIsolated`
: 2; // new apps will only crash upon concurrency violations, and will call into `checkIsolated`
static void checkUnexpectedExecutorLogLevel(void *context) {
#if SWIFT_STDLIB_HAS_ENVIRON

View File

@@ -18,6 +18,7 @@
/// swift_task_enqueueGlobalImpl
/// swift_task_enqueueGlobalWithDelayImpl
/// swift_task_enqueueMainExecutorImpl
/// swift_task_checkIsolatedImpl
/// as well as any cooperative-executor-specific functions in the runtime.
///
///===------------------------------------------------------------------===///
@@ -136,6 +137,13 @@ static void insertDelayedJob(Job *newJob, JobDeadline deadline) {
*position = newJob;
}
SWIFT_CC(swift)
static void swift_task_checkIsolatedImpl(SerialExecutorRef executor) {
_task_serialExecutor_checkIsolated(
executor.getIdentity(), swift_getObjectType(executor.getIdentity()),
executor.getSerialExecutorWitnessTable());
}
/// Insert a job into the cooperative global queue with a delay.
SWIFT_CC(swift)
static void swift_task_enqueueGlobalWithDelayImpl(JobDelay delay,

View File

@@ -18,11 +18,13 @@
/// swift_task_enqueueGlobalImpl
/// swift_task_enqueueGlobalWithDelayImpl
/// swift_task_enqueueMainExecutorImpl
/// swift_task_checkIsolated
/// as well as any Dispatch-specific functions for the runtime.
///
///===------------------------------------------------------------------===///
#if SWIFT_CONCURRENCY_ENABLE_DISPATCH
#include "swift/Runtime/HeapObject.h"
#include <dispatch/dispatch.h>
#if defined(_WIN32)
#include <Windows.h>
@@ -233,7 +235,7 @@ static void swift_task_enqueueGlobalImpl(Job *job) {
SWIFT_CC(swift)
static void swift_task_enqueueGlobalWithDelayImpl(JobDelay delay,
Job *job) {
Job *job) {
assert(job && "no job provided");
dispatch_function_t dispatchFunction = &__swift_run_job;
@@ -383,3 +385,56 @@ void swift::swift_task_enqueueOnDispatchQueue(Job *job,
auto queue = reinterpret_cast<dispatch_queue_t>(_queue);
dispatchEnqueue(queue, job, (dispatch_qos_class_t)priority, queue);
}
/// Recognize if the SerialExecutor is specifically a `DispatchSerialQueue`
/// by comparing witness tables and return it if true.
static dispatch_queue_s *getAsDispatchSerialQueue(SerialExecutorRef executor) {
if (!executor.hasSerialExecutorWitnessTable()) {
return nullptr;
}
auto executorWitnessTable = reinterpret_cast<const WitnessTable *>(
executor.getSerialExecutorWitnessTable());
auto serialQueueWitnessTable = reinterpret_cast<const WitnessTable *>(
_swift_task_getDispatchQueueSerialExecutorWitnessTable());
if (swift_compareWitnessTables(executorWitnessTable,
serialQueueWitnessTable)) {
return reinterpret_cast<dispatch_queue_s *>(executor.getIdentity());
} else {
return nullptr;
}
}
/// If the executor is a `DispatchSerialQueue` we're able to invoke the
/// dispatch's precondition API directly -- this is more efficient than going
/// through the runtime call to end up calling the same API, and also allows us
/// to perform this assertion on earlier platforms, where the `checkIsolated`
/// requirement/witness was not shipping yet.
SWIFT_CC(swift)
static void swift_task_checkIsolatedImpl(SerialExecutorRef executor) {
// If it is the main executor, compare with the Main queue
if (executor.isMainExecutor()) {
dispatch_assert_queue(dispatch_get_main_queue());
return;
}
// if able to, use the checkIsolated implementation in Swift
if (executor.hasSerialExecutorWitnessTable()) {
_task_serialExecutor_checkIsolated(
executor.getIdentity(), swift_getObjectType(executor.getIdentity()),
executor.getSerialExecutorWitnessTable());
return;
}
if (auto queue = getAsDispatchSerialQueue(executor)) {
// if the executor was not SerialExecutor for some reason but we're able
// to get a queue from it anyway, use the assert directly on it.
dispatch_assert_queue(queue); // TODO(concurrency): could we report a better message here somehow?
return;
}
// otherwise, we have no way to check, so report an error
// TODO: can we swift_getTypeName(swift_getObjectType(executor.getIdentity()), false).data safely in the message here?
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption");
}

View File

@@ -100,6 +100,43 @@ public protocol SerialExecutor: Executor {
/// perspectiveto execute code assuming one on the other.
@available(SwiftStdlib 5.9, *)
func isSameExclusiveExecutionContext(other: Self) -> Bool
/// Last resort "fallback" isolation check, called when the concurrency runtime
/// is comparing executors e.g. during ``assumeIsolated()`` and is unable to prove
/// serial equivalence between the expected (this object), and the current executor.
///
/// During executor comparison, the Swift concurrency runtime attempts to compare
/// current and expected executors in a few ways (including "complex" equality
/// between executors (see ``isSameExclusiveExecutionContext(other:)``), and if all
/// those checks fail, this method is invoked on the expected executor.
///
/// This method MUST crash if it is unable to prove that the current execution
/// context belongs to this executor. At this point usual executor comparison would
/// have already failed, though the executor may have some external tracking of
/// threads it owns, and may be able to prove isolation nevertheless.
///
/// A default implementation is provided that unconditionally crashes the
/// program, and prevents calling code from proceeding with potentially
/// not thread-safe execution.
///
/// - Warning: This method must crash and halt program execution if unable
/// to prove the isolation of the calling context.
@available(SwiftStdlib 6.0, *)
func checkIsolated()
}
@available(SwiftStdlib 6.0, *)
extension SerialExecutor {
@available(SwiftStdlib 6.0, *)
public func checkIsolated() {
#if !$Embedded
fatalError("Unexpected isolation context, expected to be executing on \(Self.self)")
#else
Builtin.int_trap()
#endif
}
}
/// An executor that may be used as preferred executor by a task.
@@ -311,16 +348,26 @@ extension UnownedTaskExecutor: Equatable {
}
}
/// Checks if the current task is running on the expected executor.
/// Returns either `true` or will CRASH if called from a different executor
/// than the passed `executor`.
///
/// This method will attempt to verify the current executor against `executor`,
/// and as a last-resort call through to `SerialExecutor.checkIsolated`.
///
/// This method will never return `false`. It either can verify we're on the
/// correct executor, or will crash the program. It should be used in
/// isolation correctness guaranteeing APIs.
///
/// Generally, Swift programs should be constructed such that it is statically
/// known that a specific executor is used, for example by using global actors or
/// custom executors. However, in some APIs it may be useful to provide an
/// additional runtime check for this, especially when moving towards Swift
/// concurrency from other runtimes which frequently use such assertions.
///
/// - Parameter executor: The expected executor.
@_spi(ConcurrencyExecutors)
@available(SwiftStdlib 5.9, *)
@_silgen_name("swift_task_isOnExecutor")
@_silgen_name("swift_task_isOnExecutor") // This function will CRASH rather than return `false`!
public func _taskIsOnExecutor<Executor: SerialExecutor>(_ executor: Executor) -> Bool
@_spi(ConcurrencyExecutors)
@@ -361,6 +408,13 @@ internal func _task_serialExecutor_isSameExclusiveExecutionContext<E>(current cu
currentExecutor.isSameExclusiveExecutionContext(other: executor)
}
@available(SwiftStdlib 6.0, *)
@_silgen_name("_task_serialExecutor_checkIsolated")
internal func _task_serialExecutor_checkIsolated<E>(executor: E)
where E: SerialExecutor {
executor.checkIsolated()
}
/// Obtain the executor ref by calling the executor's `asUnownedSerialExecutor()`.
/// The obtained executor ref will have all the user-defined flags set on the executor.
@available(SwiftStdlib 5.9, *)

View File

@@ -106,11 +106,12 @@ extension Actor {
return
}
// NOTE: This method will CRASH in new runtime versions,
// if it would have previously returned `false`.
// It will call through to SerialExecutor.checkIsolated` as a last resort.
let expectationCheck = _taskIsCurrentExecutor(self.unownedExecutor.executor)
// TODO: offer information which executor we actually got
precondition(expectationCheck,
// TODO: figure out a way to get the typed repr out of the unowned executor
"Incorrect actor executor assumption; Expected '\(self.unownedExecutor)' executor. \(message())",
file: file, line: line)
}
@@ -247,8 +248,6 @@ extension Actor {
}
guard _taskIsCurrentExecutor(self.unownedExecutor.executor) else {
// TODO: offer information which executor we actually got
// TODO: figure out a way to get the typed repr out of the unowned executor
let msg = "Incorrect actor executor assumption; Expected '\(self.unownedExecutor)' executor. \(message())"
/// TODO: implement the logic in-place perhaps rather than delegating to precondition()?
assertionFailure(msg, file: file, line: line) // short-cut so we get the exact same failure reporting semantics

View File

@@ -90,6 +90,17 @@ SWIFT_CC(swift)
void (*swift::swift_task_enqueueMainExecutor_hook)(
Job *job, swift_task_enqueueMainExecutor_original original) = nullptr;
SWIFT_CC(swift)
void (*swift::swift_task_checkIsolated_hook)(
SerialExecutorRef executor,
swift_task_checkIsolated_original original) = nullptr;
extern "C" SWIFT_CC(swift)
bool _task_serialExecutor_checkIsolated(
HeapObject *executor,
const Metadata *selfType,
const SerialExecutorWitnessTable *wtable);
#if SWIFT_CONCURRENCY_COOPERATIVE_GLOBAL_EXECUTOR
#include "CooperativeGlobalExecutor.inc"
#elif SWIFT_CONCURRENCY_ENABLE_DISPATCH
@@ -132,6 +143,13 @@ void swift::swift_task_enqueueGlobalWithDeadline(
swift_task_enqueueGlobalWithDeadlineImpl(sec, nsec, tsec, tnsec, clock, job);
}
void swift::swift_task_checkIsolated(SerialExecutorRef executor) {
if (swift_task_checkIsolated_hook)
swift_task_checkIsolated_hook(executor, swift_task_checkIsolatedImpl);
else
swift_task_checkIsolatedImpl(executor);
}
// Implemented in Swift because we need to obtain the user-defined flags on the executor ref.
//
// We could inline this with effort, though.
@@ -154,6 +172,12 @@ TaskExecutorRef _task_executor_getTaskExecutorRef(
const SerialExecutorWitnessTable *wtable);
#pragma clang diagnostic pop
/// WARNING: This method is expected to CRASH in new runtimes, and cannot be
/// used to implement "log warnings" mode. We would need a new entry point to
/// implement a "only log warnings" actor isolation checking mode, and it would
/// no be able handle more complex situations, as `SerialExecutor.checkIsolated`
/// is able to (by calling into dispatchPrecondition on old runtimes).
SWIFT_CC(swift)
static bool swift_task_isOnExecutorImpl(HeapObject *executor,
const Metadata *selfType,

View File

@@ -61,3 +61,10 @@ static void swift_task_enqueueMainExecutorImpl(Job *job) {
swift_reportError(0, "operation unsupported without libdispatch: "
"swift_task_enqueueMainExecutor");
}
SWIFT_CC(swift)
static void swift_task_checkIsolatedImpl(SerialExecutorRef executor) {
_task_serialExecutor_checkIsolated(
executor.getIdentity(), swift_getObjectType(executor.getIdentity()),
executor.getSerialExecutorWitnessTable());
}

View File

@@ -54,9 +54,7 @@ extension DistributedActor {
let unownedExecutor = self.unownedExecutor
let expectationCheck = _taskIsCurrentExecutor(unownedExecutor._executor)
// TODO: offer information which executor we actually got
precondition(expectationCheck,
// TODO: figure out a way to get the typed repr out of the unowned executor
"Incorrect actor executor assumption; Expected '\(self.unownedExecutor)' executor. \(message())",
file: file, line: line)
}
@@ -103,8 +101,6 @@ extension DistributedActor {
let unownedExecutor = self.unownedExecutor
guard _taskIsCurrentExecutor(unownedExecutor._executor) else {
// TODO: offer information which executor we actually got
// TODO: figure out a way to get the typed repr out of the unowned executor
let msg = "Incorrect actor executor assumption; Expected '\(unownedExecutor)' executor. \(message())"
/// TODO: implement the logic in-place perhaps rather than delegating to precondition()?
assertionFailure(msg, file: file, line: line) // short-cut so we get the exact same failure reporting semantics
@@ -187,6 +183,12 @@ extension DistributedActor {
}
}
/// WARNING: This function will CRASH rather than return `false` in new runtimes
///
/// It eventually calls into `SerialExecutor.checkIsolated` which allows even
/// for non Task code to assume isolation in certain situations, however this
/// API cannot be made "return false", and instead will always crash if it
/// were to return false.
@available(SwiftStdlib 5.1, *)
@usableFromInline
@_silgen_name("swift_task_isCurrentExecutor")

View File

@@ -255,6 +255,17 @@ bool useLegacySwiftObjCHashing() {
#endif
}
// FIXME(concurrency): Once the release is announced, adjust the logic detecting the SDKs
bool swift_bincompat_useLegacyNonCrashingExecutorChecks() {
#if BINARY_COMPATIBILITY_APPLE
return true; // For now, legacy behavior on Apple OSes
#elif SWIFT_TARGET_OS_DARWIN
return true; // For now, use legacy behavior on open-source builds for Apple platforms
#else
return false; // Always use the new behavior on non-Apple OSes
#endif
}
} // namespace bincompat
} // namespace runtime

View File

@@ -0,0 +1,69 @@
// RUN: %target-run-simple-swift(-parse-as-library -Xfrontend -disable-availability-checking) | %FileCheck %s
// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: concurrency_runtime
// REQUIRES: libdispatch
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: back_deploy_concurrency
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: freestanding
final class NaiveQueueExecutor: SerialExecutor {
init() {}
public func enqueue(_ job: consuming ExecutorJob) {
job.runSynchronously(on: self.asUnownedSerialExecutor())
}
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
func checkIsolated() {
print("checkIsolated: pretend it is ok!")
}
}
actor ActorOnNaiveQueueExecutor {
let executor: NaiveQueueExecutor
init() {
self.executor = NaiveQueueExecutor()
}
nonisolated var unownedExecutor: UnownedSerialExecutor {
self.executor.asUnownedSerialExecutor()
}
nonisolated func checkPreconditionIsolated() async {
print("Before preconditionIsolated")
self.preconditionIsolated()
print("After preconditionIsolated")
print("Before assumeIsolated")
self.assumeIsolated { iso in
print("Inside assumeIsolated")
}
print("After assumeIsolated")
}
}
@main struct Main {
static func main() async {
if #available(SwiftStdlib 6.0, *) {
let actor = ActorOnNaiveQueueExecutor()
await actor.checkPreconditionIsolated()
// CHECK: Before preconditionIsolated
// CHECK-NEXT: checkIsolated: pretend it is ok!
// CHECK-NEXT: After preconditionIsolated
// CHECK-NEXT: Before assumeIsolated
// CHECK-NEXT: checkIsolated: pretend it is ok!
// CHECK-NEXT: Inside assumeIsolated
// CHECK-NEXT: After assumeIsolated
}
}
}

View File

@@ -0,0 +1,69 @@
// RUN: %target-run-simple-swift(-parse-as-library -Xfrontend -disable-availability-checking) | %FileCheck %s
// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: concurrency_runtime
// REQUIRES: libdispatch
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: back_deploy_concurrency
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: freestanding
final class NaiveQueueExecutor: SerialExecutor {
init() {}
public func enqueue(_ job: consuming ExecutorJob) {
job.runSynchronously(on: self.asUnownedSerialExecutor())
}
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
func checkIsolated() {
print("checkIsolated: pretend it is ok!")
}
}
actor ActorOnNaiveQueueExecutor {
let executor: NaiveQueueExecutor
init() {
self.executor = NaiveQueueExecutor()
}
nonisolated var unownedExecutor: UnownedSerialExecutor {
self.executor.asUnownedSerialExecutor()
}
nonisolated func checkPreconditionIsolated() async {
print("Before preconditionIsolated")
self.preconditionIsolated()
print("After preconditionIsolated")
print("Before assumeIsolated")
self.assumeIsolated { iso in
print("Inside assumeIsolated")
}
print("After assumeIsolated")
}
}
@main struct Main {
static func main() async {
if #available(SwiftStdlib 6.0, *) {
let actor = ActorOnNaiveQueueExecutor()
await actor.checkPreconditionIsolated()
// CHECK: Before preconditionIsolated
// CHECK-NEXT: checkIsolated: pretend it is ok!
// CHECK-NEXT: After preconditionIsolated
// CHECK-NEXT: Before assumeIsolated
// CHECK-NEXT: checkIsolated: pretend it is ok!
// CHECK-NEXT: Inside assumeIsolated
// CHECK-NEXT: After assumeIsolated
}
}
}

View File

@@ -0,0 +1,149 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library %s -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out
// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: concurrency_runtime
// REQUIRES: libdispatch
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: back_deploy_concurrency
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: freestanding
// FIXME(concurrency): rdar://119743909 fails in optimize tests.
// UNSUPPORTED: swift_test_mode_optimize
// UNSUPPORTED: swift_test_mode_optimize_size
import StdlibUnittest
import Dispatch
// FIXME(concurrency): Dispatch should provide such implementation
extension DispatchQueue { // which includes DispatchSerialQueue, when a platform has it
public func checkIsolated() {
dispatchPrecondition(condition: .onQueue(self))
}
}
/// We only do the executor dance because missing 'asUnownedSerialExecutor'
/// on DispatchSerialQueue on some platforms, so we can't make this test
/// reliably just use a queue as the executor directly.
final class NaiveQueueExecutor: SerialExecutor {
let queue: DispatchQueue
init(queue: DispatchQueue) {
self.queue = queue
}
public func enqueue(_ unowned: UnownedJob) {
queue.sync {
unowned.runSynchronously(on: self.asUnownedSerialExecutor())
}
}
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
func checkIsolated() {
self.queue.checkIsolated()
}
}
actor ActorOnNaiveQueueExecutor {
let queue: DispatchQueue
let executor: NaiveQueueExecutor
init() {
self.queue = DispatchQueue(label: "MyQueue")
self.executor = NaiveQueueExecutor(queue: queue)
}
nonisolated var unownedExecutor: UnownedSerialExecutor {
self.executor.asUnownedSerialExecutor()
}
nonisolated func checkPreconditionIsolated() async {
print("Before queue.sync {}")
self.queue.sync {
print("Before preconditionIsolated")
self.preconditionIsolated()
print("After preconditionIsolated")
print("Before dispatchPrecondition")
dispatchPrecondition(condition: .onQueue(self.queue))
print("After preconditionIsolated")
}
}
}
actor Other {
func checkUnexpected(actor: some Actor) {
actor.assumeIsolated { isolatedActor in
print("OK")
}
}
}
// FIXME(dispatch): DispatchSerialQueue conformance to SerialExecutor not available on all platforms yet
//actor ActorOnQueue {
// let queue: DispatchSerialQueue
//
// init() {
// self.queue = DispatchSerialQueue(label: "MyQueue")
// }
//
// nonisolated var unownedExecutor: UnownedSerialExecutor {
// self.queue.asUnownedSerialExecutor()
// }
//
// nonisolated func checkPreconditionIsolated() async {
// print("Before queue.sync {}")
// self.queue.sync {
// print("Before preconditionIsolated")
// self.queue.preconditionIsolated()
// print("After preconditionIsolated")
//
// print("Before dispatchPrecondition")
// dispatchPrecondition(condition: .onQueue(self.queue))
// print("After preconditionIsolated")
// }
// }
//}
@main struct Main {
static func main() async {
let tests = TestSuite("AssertPreconditionActorExecutorCheckIsolated")
if #available(SwiftStdlib 6.0, *) {
let actorOnQueue = ActorOnNaiveQueueExecutor()
tests.test("\(ActorOnNaiveQueueExecutor.self): queue.sync { preconditionIsolated() } ") {
await actorOnQueue.checkPreconditionIsolated()
}
tests.test("\(Other.self): wrongly assume default actor on another actor's custom executor") {
expectCrashLater() // this will call into dispatch precondition, no message
let other = Other()
await other.checkUnexpected(actor: actorOnQueue)
}
tests.test("\(Other.self): correctly assume default actor on same default actor") {
let other = Other()
await other.checkUnexpected(actor: other)
}
// FIXME(dispatch): DispatchSerialQueue conformance to SerialExecutor not available on all platforms yet
// tests.test("\(ActorOnQueue.self): queue.sync { preconditionIsolated() } ") {
// await ActorOnQueue().checkPreconditionIsolated()
// }
}
await runAllTestsAsync()
}
}

View File

@@ -38,7 +38,7 @@ actor MainFriend {
}
}
func checkAssumeSomeone(someone: Someone) /* synchronous */ {
func checkAssumeSomeone(someone: SomeoneOnDefaultExecutor) /* synchronous */ {
// someone.something // can't access, would need a hop but we can't
someone.assumeIsolated { someone in
let something = someone.something
@@ -47,7 +47,7 @@ func checkAssumeSomeone(someone: Someone) /* synchronous */ {
}
}
actor Someone {
actor SomeoneOnDefaultExecutor {
func callCheckMainActor(echo: MainActorEcho) {
checkAssumeMainActor(echo: echo)
}
@@ -62,12 +62,12 @@ actor Someone {
}
actor SomeonesFriend {
let someone: Someone
let someone: SomeoneOnDefaultExecutor
nonisolated var unownedExecutor: UnownedSerialExecutor {
self.someone.unownedExecutor
}
init(someone: Someone) {
init(someone: SomeoneOnDefaultExecutor) {
self.someone = someone
}
@@ -77,8 +77,8 @@ actor SomeonesFriend {
}
actor CompleteStranger {
let someone: Someone
init(someone: Someone) {
let someone: SomeoneOnDefaultExecutor
init(someone: SomeoneOnDefaultExecutor) {
self.someone = someone
}
@@ -118,36 +118,36 @@ final class MainActorEcho {
#if !os(WASI)
tests.test("MainActor.assumeIsolated: wrongly assume the main executor, from actor on other executor") {
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected 'MainActor' executor.")
await Someone().callCheckMainActor(echo: echo)
await SomeoneOnDefaultExecutor().callCheckMainActor(echo: echo)
}
#endif
// === some Actor -------------------------------------------------------
let someone = Someone()
let someone = SomeoneOnDefaultExecutor()
#if !os(WASI)
tests.test("assumeOnActorExecutor: wrongly assume someone's executor, from 'main() async'") {
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected same executor as a.Someone.")
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected same executor as a.SomeoneOnDefaultExecutor.")
checkAssumeSomeone(someone: someone)
}
tests.test("assumeOnActorExecutor: wrongly assume someone's executor, from MainActor method") {
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected same executor as a.Someone.")
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected same executor as a.SomeoneOnDefaultExecutor.")
checkAssumeSomeone(someone: someone)
}
#endif
tests.test("assumeOnActorExecutor: assume someone's executor, from Someone") {
tests.test("assumeOnActorExecutor: assume someone's executor, from SomeoneOnDefaultExecutor") {
await someone.callCheckSomeone()
}
tests.test("assumeOnActorExecutor: assume someone's executor, from actor on the Someone.unownedExecutor") {
tests.test("assumeOnActorExecutor: assume someone's executor, from actor on the SomeoneOnDefaultExecutor.unownedExecutor") {
await SomeonesFriend(someone: someone).callCheckSomeone()
}
#if !os(WASI)
tests.test("assumeOnActorExecutor: wrongly assume the main executor, from actor on other executor") {
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected same executor as a.Someone.")
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected same executor as a.SomeoneOnDefaultExecutor.")
await CompleteStranger(someone: someone).callCheckSomeone()
}
#endif

View File

@@ -0,0 +1,94 @@
// RUN: %empty-directory(%t)
// 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
// RUN: %target-codesign %t/a.out
// NOTE: This test specifically tests the crashing behavior of `checkIsolated`,
// because this behavior is currently disabled
// RUN: env %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash %target-run %t/a.out
// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: libdispatch
// rdar://76038845
// REQUIRES: concurrency_runtime
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: single_threaded_concurrency
import StdlibUnittest
import _Concurrency
import Dispatch
// For sleep
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#endif
@MainActor func onMainActor() {
print("I'm on the main actor!")
}
func promiseMainThread(_ fn: @escaping @MainActor () -> Void) -> (() -> Void) {
typealias Fn = () -> Void
return unsafeBitCast(fn, to: Fn.self)
}
func launchTask(_ fn: @escaping () -> Void) {
if #available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 8.0, *) {
DispatchQueue.global().async {
fn()
}
}
}
func launchFromMainThread() {
launchTask(promiseMainThread(onMainActor))
}
actor MyActor {
var counter = 0
func onMyActor() {
dispatchPrecondition(condition: .notOnQueue(DispatchQueue.main))
counter = counter + 1
}
func getTaskOnMyActor() -> (() -> Void) {
return {
self.onMyActor()
}
}
}
/// These tests now eventually end up calling `dispatch_assert_queue`,
/// after the introduction of checkIsolated in `swift_task_isCurrentExecutorImpl`
@main struct Main {
static func main() {
if #available(SwiftStdlib 5.9, *) {
let tests = TestSuite("data_race_detection")
tests.test("Expect MainActor") {
expectCrashLater()
print("Launching a main-actor task")
launchFromMainThread()
sleep(1)
}
tests.test("Expect same executor") {
expectCrashLater(withMessage: "Incorrect actor executor assumption")
Task.detached {
let actor = MyActor()
let actorFn = await actor.getTaskOnMyActor()
print("Launching an actor-instance task")
launchTask(actorFn)
}
sleep(2)
}
runAllTests()
}
}
}

View File

@@ -1,7 +1,12 @@
// RUN: %empty-directory(%t)
// 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
// RUN: %target-codesign %t/a.out
// RUN: env %env-SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1 %target-run %t/a.out 2>&1 | %FileCheck %s
// We specifically test for legacy behavior here, apps compiled against old SDKs
// will be able to have this behavior, however new apps will not. We use the
// overrides to test the logic for legacy code remains functional.
//
// 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
// REQUIRES: executable_test
// REQUIRES: concurrency
@@ -61,14 +66,14 @@ actor MyActor {
struct Runner {
static func main() async {
print("Launching a main-actor task")
// CHECK: warning: data race detected: @MainActor function at main/data_race_detection.swift:25 was not called on the main thread
// CHECK: warning: data race detected: @MainActor function at main/data_race_detection_legacy_warning.swift:30 was not called on the main thread
launchFromMainThread()
sleep(1)
let actor = MyActor()
let actorFn = await actor.getTaskOnMyActor()
print("Launching an actor-instance task")
// CHECK: warning: data race detected: actor-isolated function at main/data_race_detection.swift:54 was not called on the same actor
// CHECK: warning: data race detected: actor-isolated function at main/data_race_detection_legacy_warning.swift:59 was not called on the same actor
launchTask(actorFn)
sleep(1)

View File

@@ -29,7 +29,7 @@ import Distributed
let system = LocalTestingDistributedActorSystem()
tests.test("5.7 actor, no availability executor property => no custom executor") {
expectCrashLater(withMessage: "Fatal error: Incorrect actor executor assumption; Expected 'MainActor' executor.")
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected MainActor executor")
try! await FiveSevenActor_NothingExecutor(actorSystem: system).test(x: 42)
}
@@ -38,7 +38,7 @@ import Distributed
}
tests.test("5.7 actor, 5.9 executor property => no custom executor") {
expectCrashLater(withMessage: "Fatal error: Incorrect actor executor assumption; Expected 'MainActor' executor.")
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected MainActor executor")
try! await FiveSevenActor_FiveNineExecutor(actorSystem: system).test(x: 42)
}

View File

@@ -86,19 +86,19 @@ extension ActorTest : @preconcurrency P {
//--- Crash1.swift
import Types
print(await runTest(Test.self))
// CHECK: error: data race detected: @MainActor function at Types/Types.swift:16 was not called on the main thread
// CHECK: Incorrect actor executor assumption; Expected MainActor executor
//--- Crash2.swift
import Types
print(await runAccessors(Test.self))
// CHECK: error: data race detected: @MainActor function at Types/Types.swift:15 was not called on the main thread
// CHECK: Incorrect actor executor assumption; Expected MainActor executor
//--- Crash3.swift
import Types
print(await runTest(ActorTest.self))
// CHECK: error: data race detected: actor-isolated function at Types/Types.swift:33 was not called on the same actor
// CHECK: Incorrect actor executor assumption
//--- Crash4.swift
import Types
print(await runAccessors(ActorTest.self))
// CHECK: error: data race detected: actor-isolated function at Types/Types.swift:30 was not called on the same actor
// CHECK: Incorrect actor executor assumption

View File

@@ -275,3 +275,13 @@ Added: _swift_task_pushTaskExecutorPreference
Added: _$ss26withTaskExecutorPreference_9isolation9operationxSch_pSg_ScA_pSgYixyYaq_YKXEtYaq_YKs5ErrorR_r0_lF
// async function pointer to Swift.withTaskExecutorPreference<A, B where B: Swift.Error>(_: Swift.TaskExecutor?, isolation: isolated Swift.Actor?, operation: () async throws(B) -> A) async throws(B) -> A
Added: _$ss26withTaskExecutorPreference_9isolation9operationxSch_pSg_ScA_pSgYixyYaq_YKXEtYaq_YKs5ErrorR_r0_lFTu
// === SerialExecutor.checkIsolated()
Added: _swift_task_checkIsolated
Added: _swift_task_checkIsolated_hook
// (extension in Swift):Swift.SerialExecutor.checkIsolated() -> ()
Added: _$sScfsE13checkIsolatedyyF
// dispatch thunk of Swift.SerialExecutor.checkIsolated() -> ()
Added: _$sScf13checkIsolatedyyFTj
// method descriptor for Swift.SerialExecutor.checkIsolated() -> ()
Added: _$sScf13checkIsolatedyyFTq

View File

@@ -255,3 +255,6 @@ Added: __swift_exceptionPersonality
Added: _swift_willThrowTypedImpl
Added: __swift_willThrowTypedImpl
Added: __swift_enableSwizzlingOfAllocationAndRefCountingFunctions_forInstrumentsOnly
// Runtime bincompat functions for Concurrency runtime to detect legacy mode
Added: _swift_bincompat_useLegacyNonCrashingExecutorChecks

View File

@@ -275,3 +275,13 @@ Added: _swift_task_pushTaskExecutorPreference
Added: _$ss26withTaskExecutorPreference_9isolation9operationxSch_pSg_ScA_pSgYixyYaq_YKXEtYaq_YKs5ErrorR_r0_lF
// async function pointer to Swift.withTaskExecutorPreference<A, B where B: Swift.Error>(_: Swift.TaskExecutor?, isolation: isolated Swift.Actor?, operation: () async throws(B) -> A) async throws(B) -> A
Added: _$ss26withTaskExecutorPreference_9isolation9operationxSch_pSg_ScA_pSgYixyYaq_YKXEtYaq_YKs5ErrorR_r0_lFTu
// === SerialExecutor.checkIsolated()
Added: _swift_task_checkIsolated
Added: _swift_task_checkIsolated_hook
// (extension in Swift):Swift.SerialExecutor.checkIsolated() -> ()
Added: _$sScfsE13checkIsolatedyyF
// dispatch thunk of Swift.SerialExecutor.checkIsolated() -> ()
Added: _$sScf13checkIsolatedyyFTj
// method descriptor for Swift.SerialExecutor.checkIsolated() -> ()
Added: _$sScf13checkIsolatedyyFTq

View File

@@ -255,3 +255,6 @@ Added: __swift_exceptionPersonality
Added: _swift_willThrowTypedImpl
Added: __swift_willThrowTypedImpl
Added: __swift_enableSwizzlingOfAllocationAndRefCountingFunctions_forInstrumentsOnly
// Runtime bincompat functions for Concurrency runtime to detect legacy mode
Added: _swift_bincompat_useLegacyNonCrashingExecutorChecks

View File

@@ -84,6 +84,13 @@ swift_task_enqueueGlobal_override(Job *job,
Ran = true;
}
SWIFT_CC(swift)
static void
swift_task_checkIsolated_override(SerialExecutorRef executor,
swift_task_checkIsolated_original original) {
Ran = true;
}
SWIFT_CC(swift)
static void swift_task_enqueueGlobalWithDelay_override(
unsigned long long delay, Job *job,
@@ -130,6 +137,8 @@ protected:
swift_task_enqueueGlobalWithDelay_override;
swift_task_enqueueMainExecutor_hook =
swift_task_enqueueMainExecutor_override;
swift_task_checkIsolated_hook =
swift_task_checkIsolated_override;
#ifdef RUN_ASYNC_MAIN_DRAIN_QUEUE_TEST
swift_task_asyncMainDrainQueue_hook =
swift_task_asyncMainDrainQueue_override_fn;
@@ -182,6 +191,11 @@ TEST_F(CompatibilityOverrideConcurrencyTest,
swift_task_enqueueGlobalWithDelay(0, &fakeJob);
}
TEST_F(CompatibilityOverrideConcurrencyTest,
test_swift_task_checkIsolated) {
swift_task_checkIsolated(SerialExecutorRef::generic());
}
TEST_F(CompatibilityOverrideConcurrencyTest,
test_swift_task_enqueueMainExecutor) {
swift_task_enqueueMainExecutor(&fakeJob);