Skip to content

Commit 8db7d71

Browse files
authored
Merge pull request #64271 from ktoso/wip-assert-executor-distributed
[Executors][Distributed] assumeOnLocalDistributedActorExecutor
2 parents 0bece01 + 5d2a311 commit 8db7d71

File tree

7 files changed

+174
-8
lines changed

7 files changed

+174
-8
lines changed

lib/Sema/DerivedConformanceDistributedActor.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,7 +787,6 @@ std::pair<Type, TypeDecl *> DerivedConformance::deriveDistributedActor(
787787
if (!canDeriveDistributedActor(Nominal, cast<DeclContext>(ConformanceDecl)))
788788
return std::make_pair(Type(), nullptr);
789789

790-
791790
if (assocType->getName() == Context.Id_ActorSystem) {
792791
return std::make_pair(deriveDistributedActorType_ActorSystem(*this),
793792
nullptr);

stdlib/public/Concurrency/Actor.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,7 +1993,7 @@ void swift::swift_nonDefaultDistributedActor_initialize(NonDefaultDistributedAct
19931993

19941994
OpaqueValue*
19951995
swift::swift_distributedActor_remote_initialize(const Metadata *actorType) {
1996-
auto *metadata = actorType->getClassObject();
1996+
const ClassMetadata *metadata = actorType->getClassObject();
19971997

19981998
// TODO(distributed): make this allocation smaller
19991999
// ==== Allocate the memory for the remote instance
@@ -2014,21 +2014,22 @@ swift::swift_distributedActor_remote_initialize(const Metadata *actorType) {
20142014
if (isDefaultActorClass(metadata)) {
20152015
auto actor = asImpl(reinterpret_cast<DefaultActor *>(alloc));
20162016
actor->initialize(/*remote*/true);
2017+
assert(swift_distributed_actor_is_remote(alloc));
20172018
return reinterpret_cast<OpaqueValue*>(actor);
20182019
} else {
20192020
auto actor = asImpl(reinterpret_cast<NonDefaultDistributedActor *>(alloc));
20202021
actor->initialize(/*remote*/true);
2022+
assert(swift_distributed_actor_is_remote(alloc));
2023+
return reinterpret_cast<OpaqueValue*>(actor);
20212024
}
2022-
assert(swift_distributed_actor_is_remote(alloc));
2023-
20242025
}
20252026

20262027
bool swift::swift_distributed_actor_is_remote(HeapObject *_actor) {
2027-
auto metadata = cast<ClassMetadata>(_actor->metadata);
2028+
const ClassMetadata *metadata = cast<ClassMetadata>(_actor->metadata);
20282029
if (isDefaultActorClass(metadata)) {
2029-
return asImpl((DefaultActor *) _actor)->isDistributedRemote();
2030+
return asImpl(reinterpret_cast<DefaultActor *>(_actor))->isDistributedRemote();
20302031
} else {
2031-
return asImpl((NonDefaultDistributedActor *) _actor)->isDistributedRemote(); // NEW
2032+
return asImpl(reinterpret_cast<NonDefaultDistributedActor *>(_actor))->isDistributedRemote();
20322033
}
20332034
}
20342035

stdlib/public/Concurrency/Executor.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ public struct UnownedSerialExecutor: Sendable {
5050
#if compiler(>=5.5) && $BuiltinExecutor
5151
@usableFromInline
5252
internal var executor: Builtin.Executor
53+
54+
@_spi(ConcurrencyExecutors)
55+
@available(SwiftStdlib 5.9, *)
56+
public var _executor: Builtin.Executor {
57+
self.executor
58+
}
5359
#endif
5460

5561
@inlinable
@@ -67,6 +73,7 @@ public struct UnownedSerialExecutor: Sendable {
6773
fatalError("Swift compiler is incompatible with this SDK version")
6874
#endif
6975
}
76+
7077
}
7178

7279
/// Checks if the current task is running on the expected executor.

stdlib/public/Concurrency/ExecutorAssertions.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func assumeOnMainActorExecutor<T>(
218218
/// if another actor uses the same serial executor--by using that actor's ``Actor/unownedExecutor``
219219
/// as its own ``Actor/unownedExecutor``--this check will succeed, as from a concurrency safety
220220
/// perspective, the serial executor guarantees mutual exclusion of those two actors.
221-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
221+
@available(SwiftStdlib 5.9, *)
222222
@_unavailableFromAsync(message: "express the closure as an explicit function declared on the specified 'actor' instead")
223223
public
224224
func assumeOnActorExecutor<Act: Actor, T>(
@@ -246,4 +246,5 @@ func assumeOnActorExecutor<Act: Actor, T>(
246246
}
247247

248248
// TODO(ktoso): implement assume for distributed actors as well
249+
249250
#endif // not SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY

stdlib/public/Distributed/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_swift_target_library(swiftDistributed ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS
1818
DistributedActor.cpp
1919
DistributedActor.swift
2020
DistributedActorSystem.swift
21+
DistributedAssertions.swift
2122
DistributedMetadata.swift
2223
LocalTestingDistributedActorSystem.swift
2324

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Swift
14+
@_spi(ConcurrencyExecutors) import _Concurrency
15+
16+
@available(SwiftStdlib 5.9, *)
17+
@_unavailableFromAsync(message: "express the closure as an explicit function declared on the specified 'distributed actor' instead")
18+
public
19+
func assumeOnLocalDistributedActorExecutor<Act: DistributedActor, T>(
20+
_ actor: Act,
21+
_ operation: (isolated Act) throws -> T,
22+
file: StaticString = #fileID, line: UInt = #line
23+
) rethrows -> T {
24+
typealias YesActor = (isolated Act) throws -> T
25+
typealias NoActor = (Act) throws -> T
26+
27+
guard __isLocalActor(actor) else {
28+
fatalError("Cannot assume to be 'isolated \(Act.self)' since distributed actor '\(actor)' is remote.")
29+
}
30+
31+
/// This is guaranteed to be fatal if the check fails,
32+
/// as this is our "safe" version of this API.
33+
let executor: Builtin.Executor = actor.unownedExecutor._executor
34+
guard _taskIsCurrentExecutor(executor) else {
35+
// TODO: offer information which executor we actually got when
36+
fatalError("Incorrect actor executor assumption; Expected same executor as \(actor).", file: file, line: line)
37+
}
38+
39+
// To do the unsafe cast, we have to pretend it's @escaping.
40+
return try withoutActuallyEscaping(operation) {
41+
(_ fn: @escaping YesActor) throws -> T in
42+
let rawFn = unsafeBitCast(fn, to: NoActor.self)
43+
return try rawFn(actor)
44+
}
45+
}
46+
47+
@available(SwiftStdlib 5.1, *)
48+
@usableFromInline
49+
@_silgen_name("swift_task_isCurrentExecutor")
50+
func _taskIsCurrentExecutor(_ executor: Builtin.Executor) -> Bool
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems -disable-availability-checking %S/../Inputs/FakeDistributedActorSystems.swift
3+
// RUN: %target-build-swift -Xfrontend -disable-availability-checking -parse-as-library -I %t %s %S/../Inputs/FakeDistributedActorSystems.swift -o %t/a.out
4+
// RUN: %target-codesign %t/a.out
5+
// RUN: %target-run %t/a.out
6+
7+
// REQUIRES: executable_test
8+
// REQUIRES: concurrency
9+
// REQUIRES: distributed
10+
// REQUIRES: concurrency_runtime
11+
// UNSUPPORTED: back_deployment_runtime
12+
13+
// UNSUPPORTED: back_deploy_concurrency
14+
// UNSUPPORTED: use_os_stdlib
15+
// UNSUPPORTED: freestanding
16+
17+
import StdlibUnittest
18+
import Distributed
19+
import FakeDistributedActorSystems
20+
21+
typealias DefaultDistributedActorSystem = FakeRoundtripActorSystem
22+
23+
func checkAssumeLocalDistributedActor(actor: MainFriend) /* synchronous! */ -> String {
24+
assumeOnLocalDistributedActorExecutor(actor) { dist in
25+
print("gained access to: \(dist.isolatedProperty)")
26+
return dist.isolatedProperty
27+
}
28+
}
29+
30+
func checkAssumeMainActor(actor: MainFriend) /* synchronous! */ {
31+
assumeOnMainActorExecutor {
32+
print("yay")
33+
}
34+
}
35+
36+
@MainActor
37+
func check(actor: MainFriend) {
38+
_ = checkAssumeLocalDistributedActor(actor: actor)
39+
checkAssumeMainActor(actor: actor)
40+
}
41+
42+
distributed actor MainFriend {
43+
nonisolated var unownedExecutor: UnownedSerialExecutor {
44+
print("get unowned executor")
45+
return MainActor.sharedUnownedExecutor
46+
}
47+
48+
let isolatedProperty: String = "Hello there!"
49+
50+
distributed func test(x: Int) async throws {
51+
print("executed: \(#function)")
52+
defer {
53+
print("done executed: \(#function)")
54+
}
55+
return checkAssumeMainActor(actor: self)
56+
}
57+
58+
}
59+
60+
actor OtherMain {
61+
nonisolated var unownedExecutor: UnownedSerialExecutor {
62+
return MainActor.sharedUnownedExecutor
63+
}
64+
65+
func checkAssumeLocalDistributedActor(actor: MainFriend) /* synchronous! */ {
66+
_ = assumeOnLocalDistributedActorExecutor(actor) { dist in
67+
print("gained access to: \(dist.isolatedProperty)")
68+
return dist.isolatedProperty
69+
}
70+
}
71+
}
72+
73+
@main struct Main {
74+
static func main() async {
75+
let tests = TestSuite("AssumeLocalDistributedActorExecutor")
76+
77+
let system = FakeRoundtripActorSystem()
78+
let distLocal = MainFriend(actorSystem: system)
79+
80+
if #available(SwiftStdlib 5.9, *) {
81+
82+
tests.test("assumeOnLocalDistributedActorExecutor: assume the main executor, inside the DistributedMainFriend local actor") {
83+
_ = checkAssumeLocalDistributedActor(actor: distLocal)
84+
try! await distLocal.test(x: 42)
85+
}
86+
87+
tests.test("assumeOnLocalDistributedActorExecutor: assume same actor as the DistributedMainFriend") {
88+
await OtherMain().checkAssumeLocalDistributedActor(actor: distLocal)
89+
try! await distLocal.test(x: 42)
90+
}
91+
92+
tests.test("assumeOnLocalDistributedActorExecutor: wrongly assume the same actor as the DistributedmainFriend") {
93+
await OtherMain().checkAssumeLocalDistributedActor(actor: distLocal)
94+
}
95+
96+
tests.test("assumeOnLocalDistributedActorExecutor: on remote actor reference") {
97+
expectCrashLater(withMessage: "Cannot assume to be 'isolated MainFriend' since distributed actor 'a.MainFriend' is remote.")
98+
let remoteRef = try! MainFriend.resolve(id: distLocal.id, using: system)
99+
await OtherMain().checkAssumeLocalDistributedActor(actor: remoteRef)
100+
}
101+
102+
103+
}
104+
105+
await runAllTestsAsync()
106+
}
107+
}

0 commit comments

Comments
 (0)