Skip to content

Commit 7f427c9

Browse files
committed
[Distributed] implement "roundtrip" tests and AS
1 parent e2c61d3 commit 7f427c9

File tree

2 files changed

+262
-7
lines changed

2 files changed

+262
-7
lines changed

test/Distributed/Inputs/FakeDistributedActorSystems.swift

Lines changed: 191 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import _Distributed
1818

1919
public struct ActorAddress: Hashable, Sendable, Codable {
2020
public let address: String
21-
public init(parse address : String) {
21+
22+
public init(parse address: String) {
2223
self.address = address
2324
}
2425

@@ -33,7 +34,7 @@ public struct ActorAddress: Hashable, Sendable, Codable {
3334
}
3435
}
3536

36-
// ==== Fake Transport ---------------------------------------------------------
37+
// ==== Noop Transport ---------------------------------------------------------
3738

3839
public struct FakeActorSystem: DistributedActorSystem {
3940
public typealias ActorID = ActorAddress
@@ -65,7 +66,7 @@ public struct FakeActorSystem: DistributedActorSystem {
6566

6667
public func actorReady<Act>(_ actor: Act)
6768
where Act: DistributedActor,
68-
Act.ID == ActorID {
69+
Act.ID == ActorID {
6970
}
7071

7172
public func resignID(_ id: ActorID) {
@@ -78,28 +79,27 @@ public struct FakeActorSystem: DistributedActorSystem {
7879
public func remoteCall<Act, Err, Res>(
7980
on actor: Act,
8081
target: RemoteCallTarget,
81-
invocationDecoder: inout InvocationDecoder,
82+
invocation invocationEncoder: inout InvocationEncoder,
8283
throwing: Err.Type,
8384
returning: Res.Type
8485
) async throws -> Res
8586
where Act: DistributedActor,
87+
Act.ID == ActorID,
8688
Err: Error,
87-
// Act.ID == ActorID,
8889
Res: SerializationRequirement {
8990
throw ExecuteDistributedTargetError(message: "Not implemented.")
9091
}
9192

9293
func remoteCallVoid<Act, Err>(
9394
on actor: Act,
9495
target: RemoteCallTarget,
95-
invocationDecoder: inout InvocationDecoder,
96+
invocation invocationEncoder: inout InvocationEncoder,
9697
throwing: Err.Type
9798
) async throws
9899
where Act: DistributedActor,
99100
Act.ID == ActorID,
100101
Err: Error {
101102
throw ExecuteDistributedTargetError(message: "Not implemented.")
102-
103103
}
104104
}
105105

@@ -126,3 +126,187 @@ public struct FakeInvocation: DistributedTargetInvocationEncoder, DistributedTar
126126
public typealias SerializationRequirement = Codable
127127
}
128128
}
129+
130+
// ==== Fake Roundtrip Transport -----------------------------------------------
131+
132+
// TODO: not thread safe...
133+
public final class FakeRoundtripActorSystem: DistributedActorSystem, @unchecked Sendable {
134+
public typealias ActorID = ActorAddress
135+
public typealias InvocationEncoder = FakeRoundtripInvocation
136+
public typealias InvocationDecoder = FakeRoundtripInvocation
137+
public typealias SerializationRequirement = Codable
138+
139+
var activeActors: [ActorID: any DistributedActor] = [:]
140+
141+
public init() {}
142+
143+
public func resolve<Act>(id: ActorID, as actorType: Act.Type)
144+
throws -> Act? where Act: DistributedActor {
145+
print("| resolve \(id) as remote // this system always resolves as remote")
146+
return nil
147+
}
148+
149+
public func assignID<Act>(_ actorType: Act.Type) -> ActorID
150+
where Act: DistributedActor {
151+
let id = ActorAddress(parse: "<unique-id>")
152+
print("| assign id: \(id) for \(actorType)")
153+
return id
154+
}
155+
156+
public func actorReady<Act>(_ actor: Act)
157+
where Act: DistributedActor,
158+
Act.ID == ActorID {
159+
print("| actor ready: \(actor)")
160+
self.activeActors[actor.id] = actor
161+
}
162+
163+
public func resignID(_ id: ActorID) {
164+
print("X resign id: \(id)")
165+
}
166+
167+
public func makeInvocationEncoder() -> FakeRoundtripInvocation {
168+
.init()
169+
}
170+
171+
private var remoteCallResult: Any? = nil
172+
173+
public func remoteCall<Act, Err, Res>(
174+
on actor: Act,
175+
target: RemoteCallTarget,
176+
invocation: inout InvocationEncoder,
177+
throwing errorType: Err.Type,
178+
returning returnType: Res.Type
179+
) async throws -> Res
180+
where Act: DistributedActor,
181+
Act.ID == ActorID,
182+
Err: Error,
183+
Res: SerializationRequirement {
184+
print(" >> remoteCall: on:\(actor)), target:\(target), invocation:\(invocation), throwing:\(String(reflecting: errorType)), returning:\(String(reflecting: returnType))")
185+
guard let targetActor = activeActors[actor.id] else {
186+
fatalError("Attempted to call mock 'roundtrip' on: \(actor.id) without active actor")
187+
}
188+
189+
func doIt<A: DistributedActor>(active: A) async throws -> Res {
190+
guard (actor.id) == active.id as! ActorID else {
191+
fatalError("Attempted to call mock 'roundtrip' on unknown actor: \(actor.id), known: \(active.id)")
192+
}
193+
194+
let resultHandler = FakeRoundtripResultHandler { self.remoteCallResult = $0 }
195+
try await executeDistributedTarget(
196+
on: active,
197+
mangledTargetName: target.mangledName,
198+
invocationDecoder: &invocation,
199+
handler: resultHandler
200+
)
201+
202+
print(" << remoteCall return: \(remoteCallResult!)")
203+
return remoteCallResult! as! Res
204+
}
205+
return try await _openExistential(targetActor, do: doIt)
206+
}
207+
208+
public func remoteCallVoid<Act, Err>(
209+
on actor: Act,
210+
target: RemoteCallTarget,
211+
invocation: inout InvocationEncoder,
212+
throwing: Err.Type
213+
) async throws
214+
where Act: DistributedActor,
215+
Act.ID == ActorID,
216+
Err: Error {
217+
print("remoteCallVoid: on:\(actor), target:\(target), invocation:\(invocation), throwing:\(throwing)")
218+
return ()
219+
}
220+
221+
}
222+
223+
public struct FakeRoundtripInvocation: DistributedTargetInvocationEncoder, DistributedTargetInvocationDecoder {
224+
public typealias SerializationRequirement = Codable
225+
226+
var genericSubs: [Any.Type] = []
227+
var arguments: [Any] = []
228+
var returnType: Any.Type? = nil
229+
var errorType: Any.Type? = nil
230+
231+
public mutating func recordGenericSubstitution<T>(_ type: T.Type) throws {
232+
print(" > encode generic sub: \(type)")
233+
genericSubs.append(type)
234+
}
235+
236+
public mutating func recordArgument<Argument: SerializationRequirement>(_ argument: Argument) throws {
237+
print(" > encode argument: \(argument)")
238+
arguments.append(argument)
239+
}
240+
public mutating func recordErrorType<E: Error>(_ type: E.Type) throws {
241+
print(" > encode error type: \(type)")
242+
self.errorType = type
243+
}
244+
public mutating func recordReturnType<R: SerializationRequirement>(_ type: R.Type) throws {
245+
print(" > encode return type: \(type)")
246+
self.returnType = type
247+
}
248+
public mutating func doneRecording() throws {
249+
print(" > done recording")
250+
}
251+
252+
// === decoding --------------------------------------------------------------
253+
254+
public func decodeGenericSubstitutions() throws -> [Any.Type] {
255+
print(" > decode generic subs: \(genericSubs)")
256+
return genericSubs
257+
}
258+
259+
var argumentIndex: Int = 0
260+
public mutating func decodeNextArgument<Argument>(
261+
_ argumentType: Argument.Type,
262+
into pointer: UnsafeMutablePointer<Argument>
263+
) throws {
264+
guard argumentIndex < arguments.count else {
265+
fatalError("Attempted to decode more arguments than stored! Index: \(argumentIndex), args: \(arguments)")
266+
}
267+
268+
let anyArgument = arguments[argumentIndex]
269+
guard let argument = anyArgument as? Argument else {
270+
fatalError("Cannot cast argument\(anyArgument) to expected \(Argument.self)")
271+
}
272+
273+
print(" > decode argument: \(argument)")
274+
pointer.pointee = argument
275+
argumentIndex += 1
276+
}
277+
278+
public func decodeErrorType() throws -> Any.Type? {
279+
print(" > decode return type: \(errorType.map { String(describing: $0) } ?? "nil")")
280+
return self.errorType
281+
}
282+
283+
public func decodeReturnType() throws -> Any.Type? {
284+
print(" > decode return type: \(returnType.map { String(describing: $0) } ?? "nil")")
285+
return self.returnType
286+
}
287+
}
288+
289+
@available(SwiftStdlib 5.5, *)
290+
public struct FakeRoundtripResultHandler: DistributedTargetInvocationResultHandler {
291+
public typealias SerializationRequirement = Codable
292+
293+
let store: (Any) -> Void
294+
init(_ store: @escaping (Any) -> Void) {
295+
self.store = store
296+
}
297+
298+
public func onReturn<Res>(value: Res) async throws {
299+
print(" << onReturn: \(value)")
300+
store(value)
301+
}
302+
303+
public func onThrow<Err: Error>(error: Err) async throws {
304+
print(" << onThrow: \(error)")
305+
store(error)
306+
}
307+
}
308+
309+
// ==== Helpers ----------------------------------------------------------------
310+
311+
@_silgen_name("swift_distributed_actor_is_remote")
312+
func __isRemoteActor(_ actor: AnyObject) -> Bool
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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 -module-name remoteCall -Xfrontend -enable-experimental-distributed -Xfrontend -disable-availability-checking -j2 -parse-as-library -I %t %s %S/../Inputs/FakeDistributedActorSystems.swift -o %t/a.out
4+
// RUN: %target-run %t/a.out | %FileCheck %s --enable-var-scope --color
5+
6+
// X: %target-run-simple-swift( -Xfrontend -module-name=main -Xfrontend -disable-availability-checking -Xfrontend -enable-experimental-distributed -parse-as-library -Xfrontend -I -Xfrontend %t ) | %FileCheck %s --dump-input=always
7+
8+
import FakeDistributedActorSystems
9+
10+
// REQUIRES: executable_test
11+
// REQUIRES: concurrency
12+
// REQUIRES: distributed
13+
14+
// rdar://76038845
15+
// UNSUPPORTED: use_os_stdlib
16+
// UNSUPPORTED: back_deployment_runtime
17+
18+
// FIXME(distributed): Distributed actors currently have some issues on windows, isRemote always returns false. rdar://82593574
19+
// UNSUPPORTED: windows
20+
21+
// FIXME(distributed): remote calls seem to hang on linux - rdar://87240034
22+
// UNSUPPORTED: linux
23+
24+
import _Distributed
25+
import FakeDistributedActorSystems
26+
27+
typealias DefaultDistributedActorSystem = FakeRoundtripActorSystem
28+
29+
distributed actor Greeter: CustomStringConvertible {
30+
distributed func echo(name: String) -> String {
31+
return "Echo: \(name) (impl on: \(self.id))"
32+
}
33+
34+
distributed func error() throws -> String {
35+
throw SomeError()
36+
}
37+
38+
nonisolated var description: String {
39+
"\(Self.self)(\(id))"
40+
}
41+
}
42+
43+
struct SomeError: Error {}
44+
45+
// ==== Test -------------------------------------------------------------------
46+
47+
func test() async throws {
48+
let system = DefaultDistributedActorSystem()
49+
50+
let local = Greeter(system: system)
51+
let ref = try Greeter.resolve(id: local.id, using: system)
52+
53+
let reply = try await ref.echo(name: "Caplin")
54+
// CHECK: > encode argument: Caplin
55+
// CHECK-NOT: > encode error type
56+
// CHECK: > encode return type: String
57+
// CHECK: > done recording
58+
// CHECK: >> remoteCall
59+
// CHECK: > decode argument: Caplin
60+
// CHECK: > decode return type: String
61+
// CHECK: << onReturn: Echo: Caplin (impl on: ActorAddress(address: "<unique-id>"))
62+
63+
print("got: \(reply)")
64+
// CHECK: got: Echo: Caplin (impl on: ActorAddress(address: "<unique-id>"))
65+
}
66+
67+
@main struct Main {
68+
static func main() async {
69+
try! await test()
70+
}
71+
}

0 commit comments

Comments
 (0)