[Concurrency] Implement detecting isIsolatingCurrentContext user impls (#79946)

* [Concurrency] Detect non-default impls of isIsolatingCurrentContext

* [Concurrency] No need for trailing info about isIsolating... in conformance

* Apply changes from review
This commit is contained in:
Konrad `ktoso` Malawski
2025-03-18 09:39:11 +09:00
committed by GitHub
parent 1c78d0c683
commit 85fcd69833
13 changed files with 396 additions and 153 deletions

View File

@@ -2930,6 +2930,14 @@ public:
return Demangle::makeSymbolicMangledNameStringRef(this->template getTrailingObjects<TargetGlobalActorReference<Runtime>>()->type);
}
/// True if this is a conformance to 'SerialExecutor' which has a non-default
/// (i.e. not the stdlib's default implementation) witness. This means that
/// the developer has implemented this method explicitly and we should prefer
/// calling it.
bool hasNonDefaultSerialExecutorIsIsolatingCurrentContext() const {
return Flags.hasNonDefaultSerialExecutorIsIsolatingCurrentContext();
}
/// Retrieve the protocol conformance of the global actor type to the
/// GlobalActor protocol.
const TargetProtocolConformanceDescriptor<Runtime> *

View File

@@ -748,6 +748,14 @@ private:
IsConformanceOfProtocolMask = 0x01u << 18,
HasGlobalActorIsolation = 0x01u << 19,
// Used to detect if this is a conformance to SerialExecutor that has
// an user defined implementation of 'isIsolatingCurrentContext'. This
// requirement is special in the sense that if a non-default impl is present
// we will avoid calling the `checkIsolated` method which would lead to a
// crash. In other words, this API "soft replaces" 'checkIsolated' so we
// must at runtime the presence of a non-default implementation.
HasNonDefaultSerialExecutorIsIsolatingCurrentContext = 0x01u << 20,
NumConditionalPackDescriptorsMask = 0xFFu << 24,
NumConditionalPackDescriptorsShift = 24
};
@@ -813,7 +821,15 @@ public:
? HasGlobalActorIsolation
: 0));
}
ConformanceFlags withHasNonDefaultSerialExecutorIsIsolatingCurrentContext(
bool hasNonDefaultSerialExecutorIsIsolatingCurrentContext) const {
return ConformanceFlags((Value & ~HasNonDefaultSerialExecutorIsIsolatingCurrentContext)
| (hasNonDefaultSerialExecutorIsIsolatingCurrentContext
? HasNonDefaultSerialExecutorIsIsolatingCurrentContext
: 0));
}
/// Retrieve the type reference kind kind.
TypeReferenceKind getTypeReferenceKind() const {
return TypeReferenceKind(
@@ -858,6 +874,10 @@ public:
return Value & HasGlobalActorIsolation;
}
bool hasNonDefaultSerialExecutorIsIsolatingCurrentContext() const {
return Value & HasNonDefaultSerialExecutorIsIsolatingCurrentContext;
}
/// Retrieve the # of conditional requirements.
unsigned getNumConditionalRequirements() const {
return (Value & NumConditionalRequirementsMask)

View File

@@ -118,6 +118,7 @@ IDENTIFIER(makeIterator)
IDENTIFIER(makeAsyncIterator)
IDENTIFIER(nestedContainer)
IDENTIFIER(isEmpty)
IDENTIFIER(isIsolatingCurrentContext)
IDENTIFIER(isolation)
IDENTIFIER(Iterator)
IDENTIFIER(AsyncIterator)

View File

@@ -1158,6 +1158,9 @@ public:
/// \returns true if this module is the "swift" standard library module.
bool isStdlibModule() const;
/// \returns true if this module is the "_Concurrency" standard library module.
bool isConcurrencyModule() const;
/// \returns true if this module has standard substitutions for mangling.
bool hasStandardSubstitutions() const;

View File

@@ -80,7 +80,7 @@ bool useLegacySwiftObjCHashing();
/// - if not available, it will invoke the the *crashing* 'checkIsolated'
///
/// This can be overridden by using `SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1`
/// or `SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash|nocrash|swift6|swift6.2`
/// or `SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash|nocrash|swift6|isIsolatingCurrentContext`
SWIFT_RUNTIME_STDLIB_SPI
bool swift_bincompat_useLegacyNonCrashingExecutorChecks();

View File

@@ -1023,6 +1023,7 @@ bool swift_task_isCurrentExecutor(SerialExecutorRef executor);
/// this could be a pointer to a different enum instance if we need it to be.
enum swift_task_is_current_executor_flag : uint64_t {
/// We aren't passing any flags.
/// Effectively this is a backwards compatible mode.
None = 0x0,
/// This is not used today, but is just future ABI reservation.
@@ -1047,9 +1048,15 @@ enum swift_task_is_current_executor_flag : uint64_t {
/// The routine should assert on failure.
Assert = 0x8,
/// The routine MUST NOT assert on failure.
/// Even at the cost of not calling 'checkIsolated' if it is available.
MustNotAssert = 0x10,
/// The routine should use 'isIsolatingCurrentContext' function on the
/// 'expected' executor instead of `checkIsolated`.
HasIsIsolatingCurrentContext = 0x10,
///
/// This is a variant of `MustNotAssert`
UseIsIsolatingCurrentContext = 0x20,
};
SWIFT_EXPORT_FROM(swift_Concurrency)

View File

@@ -2031,6 +2031,10 @@ bool ModuleDecl::isStdlibModule() const {
return !getParent() && getName() == getASTContext().StdlibModuleName;
}
bool ModuleDecl::isConcurrencyModule() const {
return !getParent() && getName() == getASTContext().Id_Concurrency;
}
bool ModuleDecl::hasStandardSubstitutions() const {
return !getParent() &&
(getName() == getASTContext().StdlibModuleName ||
@@ -4285,11 +4289,6 @@ struct SwiftSettingsWalker : ASTWalker {
} // namespace
static bool isConcurrencyModule(DeclContext *dc) {
auto *m = dc->getParentModule();
return !m->getParent() && m->getName() == m->getASTContext().Id_Concurrency;
}
bool SwiftSettingsWalker::isSwiftSettingsMacroExpr(
MacroExpansionExpr *macroExpr) {
// First make sure we actually have a macro with the name SwiftSettings.
@@ -4406,7 +4405,8 @@ SwiftSettingsWalker::patternMatchDefaultIsolationMainActor(CallExpr *callExpr) {
if (!nomDecl)
return CanType();
auto *nomDeclDC = nomDecl->getDeclContext();
if (!nomDeclDC->isModuleScopeContext() || !isConcurrencyModule(nomDeclDC))
auto *nomDeclModule = nomDecl->getParentModule();
if (!nomDeclDC->isModuleScopeContext() || !nomDeclModule->isConcurrencyModule())
return CanType();
return nomDecl->getDeclaredType()->getCanonicalType();

View File

@@ -2238,6 +2238,7 @@ namespace {
Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique());
Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol());
Flags = Flags.withHasGlobalActorIsolation(isolation.isGlobalActor());
Flags = withSerialExecutorCheckingModeFlags(Flags, conf);
} else {
Flags = Flags.withIsRetroactive(false)
.withIsSynthesizedNonUnique(false);
@@ -2459,6 +2460,32 @@ namespace {
rootGlobalActorConformance));
B.addRelativeAddress(globalActorConformanceDescriptor);
}
static ConformanceFlags
withSerialExecutorCheckingModeFlags(ConformanceFlags Flags, const NormalProtocolConformance *conf) {
ProtocolDecl *proto = conf->getProtocol();
auto &C = proto->getASTContext();
ConformanceFlags UpdatedFlags = Flags;
if (proto->isSpecificProtocol(swift::KnownProtocolKind::SerialExecutor)) {
conf->forEachValueWitness([&](const ValueDecl *req,
Witness witness) {
bool nameMatch = witness.getDecl()->getBaseIdentifier() == C.Id_isIsolatingCurrentContext;
if (nameMatch) {
if (DeclContext *NominalOrExtension = witness.getDecl()->getDeclContext()) {
// If the witness is NOT the default implementation in the _Concurrency library,
// we should record that this is an user provided implementation and we should call it.
bool hasNonDefaultIsIsolatingCurrentContext =
!NominalOrExtension->getParentModule()->isConcurrencyModule();
UpdatedFlags = UpdatedFlags.withHasNonDefaultSerialExecutorIsIsolatingCurrentContext(
hasNonDefaultIsIsolatingCurrentContext);
}
}
});
}
return UpdatedFlags;
}
};
}

View File

@@ -351,17 +351,43 @@ enum IsCurrentExecutorCheckMode : unsigned {
Legacy_NoCheckIsolated_NonCrashing,
};
namespace {
using SwiftTaskIsCurrentExecutorOptions =
OptionSet<swift_task_is_current_executor_flag>;
}
static void _swift_task_debug_dumpIsCurrentExecutorFlags(
const char *hint,
swift_task_is_current_executor_flag flags) {
if (flags == swift_task_is_current_executor_flag::None) {
SWIFT_TASK_DEBUG_LOG("%s swift_task_is_current_executor_flag::%s",
hint, "None");
return;
}
auto options = SwiftTaskIsCurrentExecutorOptions(flags);
if (options.contains(swift_task_is_current_executor_flag::Assert))
SWIFT_TASK_DEBUG_LOG("%s swift_task_is_current_executor_flag::%s",
hint, "Assert");
if (options.contains(swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext))
SWIFT_TASK_DEBUG_LOG("%s swift_task_is_current_executor_flag::%s",
hint, "UseIsIsolatingCurrentContext");
}
// Shimming call to Swift runtime because Swift Embedded does not have
// these symbols defined.
swift_task_is_current_executor_flag
__swift_bincompat_useLegacyNonCrashingExecutorChecks() {
swift_task_is_current_executor_flag options = swift_task_is_current_executor_flag::None;
#if !SWIFT_CONCURRENCY_EMBEDDED
if (swift::runtime::bincompat::
if (!swift::runtime::bincompat::
swift_bincompat_useLegacyNonCrashingExecutorChecks()) {
return swift_task_is_current_executor_flag::None;
options = swift_task_is_current_executor_flag(
options | swift_task_is_current_executor_flag::Assert);
}
#endif
return swift_task_is_current_executor_flag::Assert;
_swift_task_debug_dumpIsCurrentExecutorFlags("runtime linking determined default mode", options);
return options;
}
// Shimming call to Swift runtime because Swift Embedded does not have
@@ -376,47 +402,47 @@ const char *__swift_runtime_env_useLegacyNonCrashingExecutorChecks() {
#endif
}
// Determine the default effective executor checking mode, and apply environment
// variable overrides of the executor checking mode.
// Done this way because of the interaction with the initial value of
// 'unexpectedExecutorLogLevel'
swift_task_is_current_executor_flag swift_bincompat_useLegacyNonCrashingExecutorChecks() {
// 'unexpectedExecutorLogLevel'.
swift_task_is_current_executor_flag swift_bincompat_selectDefaultIsCurrentExecutorCheckingMode() {
// Default options as determined by linked runtime,
// i.e. very old runtimes were not allowed to crash but then we introduced 'checkIsolated'
// which was allowed to crash;
swift_task_is_current_executor_flag options =
__swift_bincompat_useLegacyNonCrashingExecutorChecks();
// Potentially, override the platform detected mode, primarily used in tests.
if (const char *modeStr =
__swift_runtime_env_useLegacyNonCrashingExecutorChecks()) {
if (strlen(modeStr) == 0) {
return swift_task_is_current_executor_flag::None;
_swift_task_debug_dumpIsCurrentExecutorFlags("mode override is empty", options);
return options;
}
if (strcmp(modeStr, "nocrash") == 0 ||
strcmp(modeStr, "legacy") == 0) {
// Since we're in nocrash/legacy mode:
// Remove the assert option which is what would cause the "crash" mode
options = swift_task_is_current_executor_flag(
options & ~swift_task_is_current_executor_flag::Assert);
SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = %s => NONE; options = %d",
modeStr, options);
} else if (strcmp(modeStr, "swift6.2") == 0) {
// Since we're in nocrash/legacy mode:
// Remove the assert option which is what would cause the "crash" mode
options = swift_task_is_current_executor_flag(
options | swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext);
options & ~swift_task_is_current_executor_flag::Assert);
} else if (strcmp(modeStr, "isIsolatingCurrentContext") == 0) {
options = swift_task_is_current_executor_flag(
options | swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext);
// When we're using the isIsolatingCurrentContext we don't want to use crashing APIs,
// so disable it explicitly.
options = swift_task_is_current_executor_flag(
options & ~swift_task_is_current_executor_flag::Assert);
SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = %s => 6.2; options = %d", modeStr, options);
} else if (strcmp(modeStr, "crash") == 0 ||
strcmp(modeStr, "swift6") == 0) {
options = swift_task_is_current_executor_flag(
options | swift_task_is_current_executor_flag::Assert);
SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = %s => 6.9; options = %d", modeStr, options);
}
// else { // else, just use the platform detected mode
// assert(strlen(modeStr) > 0 && "why is it empty!");
// }
} // else, just use the platform detected mode
} // no override, use the default mode
SWIFT_TASK_DEBUG_LOG("executor checking: final options = %d", options);
return options;
}
@@ -434,24 +460,40 @@ extern "C" SWIFT_CC(swift) void _swift_task_enqueueOnExecutor(
Job *job, HeapObject *executor, const Metadata *executorType,
const SerialExecutorWitnessTable *wtable);
namespace {
using SwiftTaskIsCurrentExecutorOptions =
OptionSet<swift_task_is_current_executor_flag>;
/// Check the executor's witness table for specific information about e.g.
/// being able ignore `checkIsolated` and only call `isIsolatingCurrentContext`.
static swift_task_is_current_executor_flag
_getIsolationCheckingOptionsFromExecutorWitnessTable(const SerialExecutorWitnessTable *_wtable) {
const WitnessTable* wtable = reinterpret_cast<const WitnessTable*>(_wtable);
auto description = wtable->getDescription();
if (!description) {
return swift_task_is_current_executor_flag::None;
}
if (description->hasNonDefaultSerialExecutorIsIsolatingCurrentContext()) {
// The specific executor has implemented `isIsolatingCurrentContext` and
// we do not have to call `checkIsolated`.
return swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext;
}
// No changes to the checking mode.
return swift_task_is_current_executor_flag::None;
}
SWIFT_CC(swift)
static bool swift_task_isCurrentExecutorWithFlagsImpl(
SerialExecutorRef expectedExecutor,
swift_task_is_current_executor_flag flags) {
auto options = SwiftTaskIsCurrentExecutorOptions(flags);
auto current = ExecutorTrackingInfo::current();
SWIFT_TASK_DEBUG_LOG("executor checking: current task %p", current);
#ifndef NDEBUG
if (options.contains(swift_task_is_current_executor_flag::Assert))
SWIFT_TASK_DEBUG_LOG("executor checking: active option = Assert (%d)", flags);
if (options.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext))
SWIFT_TASK_DEBUG_LOG("executor checking: active option = HasIsIsolatingCurrentContext (%d)", flags);
#endif
if (expectedExecutor.getIdentity() && expectedExecutor.hasSerialExecutorWitnessTable()) {
if (auto *wtable = expectedExecutor.getSerialExecutorWitnessTable()) {
auto executorSpecificMode = _getIsolationCheckingOptionsFromExecutorWitnessTable(wtable);
flags = swift_task_is_current_executor_flag(flags | executorSpecificMode);
}
}
auto options = SwiftTaskIsCurrentExecutorOptions(flags);
_swift_task_debug_dumpIsCurrentExecutorFlags(__FUNCTION__, flags);
if (!current) {
// We have no current executor, i.e. we are running "outside" of Swift
@@ -468,14 +510,14 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
// We cannot use 'complexEquality' as it requires two executor instances,
// and we do not have a 'current' executor here.
if (options.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext)) {
SWIFT_TASK_DEBUG_LOG("executor checking mode option: HasIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext",
if (options.contains(swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext)) {
SWIFT_TASK_DEBUG_LOG("executor checking mode option: UseIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext",
expectedExecutor.getIdentity());
// The executor has the most recent 'isIsolatingCurrentContext' API
// so available so we prefer calling that to 'checkIsolated'.
auto result = swift_task_isIsolatingCurrentContext(expectedExecutor);
SWIFT_TASK_DEBUG_LOG("executor checking mode option: HasIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext => %s",
SWIFT_TASK_DEBUG_LOG("executor checking mode option: UseIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext => %s",
expectedExecutor.getIdentity(), result ? "pass" : "fail");
return result;
}
@@ -553,6 +595,8 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
// We may be able to prove we're on the same executor as expected by
// using 'checkIsolated' later on though.
if (expectedExecutor.isComplexEquality()) {
SWIFT_TASK_DEBUG_LOG("executor checking: expectedExecutor is complex equality (%p)",
expectedExecutor.getIdentity());
if (currentExecutor.getIdentity() &&
currentExecutor.hasSerialExecutorWitnessTable() &&
expectedExecutor.getIdentity() &&
@@ -582,16 +626,20 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
}
}
if (options.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext)) {
// We can invoke the 'isIsolatingCurrentContext' function.
// Invoke the 'isIsolatingCurrentContext' function if we can; If so, we can
// avoid calling the `checkIsolated` because their result will be the same.
if (options.contains(swift_task_is_current_executor_flag::UseIsIsolatingCurrentContext)) {
SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext",
expectedExecutor.getIdentity());
expectedExecutor.getIdentity());
bool checkResult = swift_task_isIsolatingCurrentContext(expectedExecutor);
SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext => %p",
expectedExecutor.getIdentity(), checkResult ? "pass" : "fail");
return checkResult;
} else {
SWIFT_TASK_DEBUG_LOG("executor checking: can NOT call (%p).isIsolatingCurrentContext",
expectedExecutor.getIdentity());
}
// This provides a last-resort check by giving the expected SerialExecutor the
@@ -623,6 +671,9 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
SWIFT_TASK_DEBUG_LOG("executor checking: call (%p).checkIsolated passed => pass",
expectedExecutor.getIdentity());
return true;
} else {
SWIFT_TASK_DEBUG_LOG("executor checking: can NOT call (%p).checkIsolated",
expectedExecutor.getIdentity());
}
// In the end, since 'checkIsolated' could not be used, so we must assume
@@ -633,30 +684,15 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl(
// Check override of executor checking mode.
static void swift_task_setDefaultExecutorCheckingFlags(void *context) {
SWIFT_TASK_DEBUG_LOG("executor checking: swift_task_setDefaultExecutorCheckingFlags = %d", nullptr);
auto useLegacyMode = swift_bincompat_useLegacyNonCrashingExecutorChecks();
SWIFT_TASK_DEBUG_LOG("executor checking: use legacy mode = %d", useLegacyMode);
auto *options = static_cast<swift_task_is_current_executor_flag *>(context);
if (useLegacyMode) {
*options = useLegacyMode;
}
// else {
// *options = swift_task_is_current_executor_flag(
// *options | swift_task_is_current_executor_flag::Assert);
// SWIFT_TASK_DEBUG_LOG("executor checking: ADD ASSERT -> %d", *options);
// }
// FIXME: remove this
*options = swift_task_is_current_executor_flag(
*options | swift_task_is_current_executor_flag::None);
auto modeOverride = swift_bincompat_selectDefaultIsCurrentExecutorCheckingMode();
if (modeOverride != swift_task_is_current_executor_flag::None) {
*options = modeOverride;
}
SWIFT_TASK_DEBUG_LOG("executor checking: resulting options = %d", *options);
SWIFT_TASK_DEBUG_LOG("executor checking: option Assert = %d",
SwiftTaskIsCurrentExecutorOptions(*options)
.contains(swift_task_is_current_executor_flag::Assert));
SWIFT_TASK_DEBUG_LOG("executor checking: option HasIsIsolatingCurrentContext = %d",
SwiftTaskIsCurrentExecutorOptions(*options)
.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext));
_swift_task_debug_dumpIsCurrentExecutorFlags(__FUNCTION__, *options);
}
SWIFT_CC(swift)
@@ -689,7 +725,7 @@ swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor) {
/// 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()
(swift_bincompat_selectDefaultIsCurrentExecutorCheckingMode() == swift_task_is_current_executor_flag::None)
? 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`
@@ -701,9 +737,9 @@ static void checkUnexpectedExecutorLogLevel(void *context) {
long level = strtol(levelStr, nullptr, 0);
if (level >= 0 && level < 3) {
auto flag = SwiftTaskIsCurrentExecutorOptions(
swift_bincompat_useLegacyNonCrashingExecutorChecks());
if (flag.contains(swift_task_is_current_executor_flag::Assert)) {
auto options = SwiftTaskIsCurrentExecutorOptions(
swift_bincompat_selectDefaultIsCurrentExecutorCheckingMode());
if (options.contains(swift_task_is_current_executor_flag::Assert)) {
// We are in swift6/crash mode of isCurrentExecutor which means that
// rather than returning false, that method will always CRASH when an
// executor mismatch is discovered.

View File

@@ -126,11 +126,11 @@ VARIABLE(SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE, string, "",
"legacy 'nocrash' behavior until adopting code has been adjusted. "
"It is possible to force the use of Swift 6.2's "
"isIsolatingCurrentContext instead of checkIsolated by passing "
" the 'swift6.2' configuration value. "
" the 'isIsolatingCurrentContext' configuration value. "
"Legal values are: "
" 'legacy' (Legacy behavior), "
" 'swift6' (Swift 6.0-6.1 behavior)"
" 'swift6.2' (Swift 6.2 behavior)")
" 'isIsolatingCurrentContext' (Swift 6.2 behavior)")
VARIABLE(SWIFT_DUMP_ACCESSIBLE_FUNCTIONS, bool, false,
"Dump a listing of all 'AccessibleFunctionRecord's upon first access. "

View File

@@ -0,0 +1,117 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %import-libdispatch -parse-as-library %s -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | %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
@available(SwiftStdlib 6.2, *)
final class IsIsolatingExecutor: SerialExecutor {
init() {}
func enqueue(_ job: consuming ExecutorJob) {
job.runSynchronously(on: self.asUnownedSerialExecutor())
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
func checkIsolated() {
print("called: checkIsolated")
}
func isIsolatingCurrentContext() -> Bool {
print("called: isIsolatingCurrentContext")
return true
}
}
@available(SwiftStdlib 6.2, *)
final class NoChecksImplementedExecutor: SerialExecutor {
init() {}
func enqueue(_ job: consuming ExecutorJob) {
job.runSynchronously(on: self.asUnownedSerialExecutor())
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
}
@available(SwiftStdlib 6.2, *)
final class JustCheckIsolatedExecutor: SerialExecutor {
init() {}
func enqueue(_ job: consuming ExecutorJob) {
job.runSynchronously(on: self.asUnownedSerialExecutor())
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
func checkIsolated() {
print("called: checkIsolated")
}
}
@available(SwiftStdlib 6.2, *)
actor ActorOnIsCheckImplementingExecutor<Ex: SerialExecutor> {
let executor: Ex
init(on executor: Ex) {
self.executor = executor
}
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.2, *) {
let hasIsIsolatingCurrentContextExecutor = IsIsolatingExecutor()
let justCheckIsolatedExecutor = JustCheckIsolatedExecutor()
print("=== do not crash with executor which implements isIsolatingCurrentContext")
let hasIsCheckActor = ActorOnIsCheckImplementingExecutor(on: hasIsIsolatingCurrentContextExecutor)
await hasIsCheckActor.checkPreconditionIsolated()
// CHECK: Before preconditionIsolated
// CHECK-NOT: called: checkIsolated
// CHECK: called: isIsolatingCurrentContext
// CHECK-NOT: called: checkIsolated
// CHECK: After preconditionIsolated
// CHECK: Before assumeIsolated
// CHECK-NOT: called: checkIsolated
// CHECK: called: isIsolatingCurrentContext
// CHECK-NOT: called: checkIsolated
// CHECK: After assumeIsolated
}
}
}

View File

@@ -1,80 +0,0 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %import-libdispatch -parse-as-library %s -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6.2 %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
@available(SwiftStdlib 6.2, *)
final class NaiveQueueExecutor: SerialExecutor {
init() {}
func enqueue(_ job: consuming ExecutorJob) {
job.runSynchronously(on: self.asUnownedSerialExecutor())
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
func checkIsolated() {
fatalError("Should not call this: checkIsolated")
}
func isIsolatingCurrentContext() -> Bool {
print("pretend it is ok: isIsolatingCurrentContext")
return true
}
}
@available(SwiftStdlib 6.2, *)
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.2, *) {
let actor = ActorOnNaiveQueueExecutor()
await actor.checkPreconditionIsolated()
// CHECK: Before preconditionIsolated
// CHECK-NOT: Should not call this: checkIsolated
// CHECK-NEXT: pretend it is ok: isIsolatingCurrentContext
// CHECK-NEXT: After preconditionIsolated
// CHECK-NEXT: Before assumeIsolated
// CHECK-NOT: Should not call this: checkIsolated
// CHECK-NEXT: pretend it is ok: isIsolatingCurrentContext
// CHECK-NEXT: After assumeIsolated
}
}
}

View File

@@ -0,0 +1,104 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %import-libdispatch -parse-as-library %s -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | %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
@available(SwiftStdlib 6.2, *)
final class IsIsolatingExecutor: SerialExecutor {
init() {}
func enqueue(_ job: consuming ExecutorJob) {
job.runSynchronously(on: self.asUnownedSerialExecutor())
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
func checkIsolated() {
print("called: checkIsolated")
}
func isIsolatingCurrentContext() -> Bool {
print("called: isIsolatingCurrentContext")
return true
}
}
@available(SwiftStdlib 6.2, *)
final class JustCheckIsolatedExecutor: SerialExecutor {
init() {}
func enqueue(_ job: consuming ExecutorJob) {
job.runSynchronously(on: self.asUnownedSerialExecutor())
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}
func checkIsolated() {
print("called: checkIsolated")
}
}
@available(SwiftStdlib 6.2, *)
actor ActorOnIsCheckImplementingExecutor<Ex: SerialExecutor> {
let executor: Ex
init(on executor: Ex) {
self.executor = executor
}
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.2, *) {
let hasIsIsolatingCurrentContextExecutor = IsIsolatingExecutor()
let justCheckIsolatedExecutor = JustCheckIsolatedExecutor()
print("do checkIsolated with executor which does NOT implement isIsolatingCurrentContext")
let checkIsolatedActor = ActorOnIsCheckImplementingExecutor(on: justCheckIsolatedExecutor)
await checkIsolatedActor.checkPreconditionIsolated()
// CHECK: Before preconditionIsolated
// CHECK-NOT: called: isIsolatingCurrentContext
// CHECK: called: checkIsolated
// CHECK-NOT: called: isIsolatingCurrentContext
// CHECK: After preconditionIsolated
// CHECK: Before assumeIsolated
// CHECK-NOT: called: isIsolatingCurrentContext
// CHECK: called: checkIsolated
// CHECK-NOT: called: isIsolatingCurrentContext
// CHECK: After assumeIsolated
}
}
}