Skip to content

Commit e237b4b

Browse files
authored
🍒 [5.7][Distributed] Dont hang calls on not found accessors #60266 (#60476)
1 parent 25d8029 commit e237b4b

File tree

5 files changed

+148
-10
lines changed

5 files changed

+148
-10
lines changed

stdlib/public/Distributed/DistributedActor.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ static void ::swift_distributed_execute_target_resume(
109109
return resumeInParent(parentCtx, error);
110110
}
111111

112+
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
113+
SwiftError* swift_distributed_makeDistributedTargetAccessorNotFoundError(
114+
const char *targetNameStart, size_t targetNameLength);
115+
112116
SWIFT_CC(swiftasync)
113117
void ::swift_distributed_execute_target(
114118
SWIFT_ASYNC_CONTEXT AsyncContext *callerContext,
@@ -124,8 +128,12 @@ void ::swift_distributed_execute_target(
124128
void **decoderWitnessTable) {
125129
auto *accessor = findDistributedAccessor(targetNameStart, targetNameLength);
126130
if (!accessor) {
127-
assert(false && "no distributed accessor");
128-
return; // FIXME(distributed): return -1 here so the lib can fail the call
131+
SwiftError *error =
132+
swift_distributed_makeDistributedTargetAccessorNotFoundError(targetNameStart, targetNameLength);
133+
auto resumeInParent =
134+
reinterpret_cast<TargetExecutorSignature::ContinuationType *>(
135+
callerContext->ResumeParent);
136+
return resumeInParent(callerContext, error);
129137
}
130138

131139
auto *asyncFnPtr = reinterpret_cast<

stdlib/public/Distributed/DistributedActorSystem.swift

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ extension DistributedActorSystem {
207207
/// some other mismatch between them happens. In general, this
208208
/// method is allowed to throw in any situation that might otherwise
209209
/// result in an illegal or unexpected invocation being performed.
210+
///
211+
/// Throws ``ExecuteDistributedTargetMissingAccessorError`` if the `target`
212+
/// does not resolve to a valid distributed function accessor, i.e. the
213+
/// call identifier is incorrect, corrupted, or simply not present in this process.
210214
public func executeDistributedTarget<Act>(
211215
on actor: Act,
212216
target: RemoteCallTarget,
@@ -242,7 +246,8 @@ extension DistributedActorSystem {
242246
let subs = try invocationDecoder.decodeGenericSubstitutions()
243247
if subs.isEmpty {
244248
throw ExecuteDistributedTargetError(
245-
message: "Cannot call generic method without generic argument substitutions")
249+
message: "Cannot call generic method without generic argument substitutions",
250+
errorCode: .missingGenericSubstitutions)
246251
}
247252

248253
substitutionsBuffer = .allocate(capacity: subs.count)
@@ -256,7 +261,8 @@ extension DistributedActorSystem {
256261
genericArguments: substitutionsBuffer!)
257262
if numWitnessTables < 0 {
258263
throw ExecuteDistributedTargetError(
259-
message: "Generic substitutions \(subs) do not satisfy generic requirements of \(target) (\(targetName))")
264+
message: "Generic substitutions \(subs) do not satisfy generic requirements of \(target) (\(targetName))",
265+
errorCode: .invalidGenericSubstitutions)
260266
}
261267
}
262268

@@ -270,7 +276,8 @@ extension DistributedActorSystem {
270276
Failed to decode distributed invocation target expected parameter count,
271277
error code: \(paramCount)
272278
mangled name: \(targetName)
273-
""")
279+
""",
280+
errorCode: .invalidParameterCount)
274281
}
275282

276283
// Prepare buffer for the parameter types to be decoded into:
@@ -295,7 +302,8 @@ extension DistributedActorSystem {
295302
Failed to decode the expected number of params of distributed invocation target, error code: \(decodedNum)
296303
(decoded: \(decodedNum), expected params: \(paramCount)
297304
mangled name: \(targetName)
298-
""")
305+
""",
306+
errorCode: .invalidParameterCount)
299307
}
300308

301309
// Copy the types from the buffer into a Swift Array
@@ -316,12 +324,14 @@ extension DistributedActorSystem {
316324
genericEnv: genericEnv,
317325
genericArguments: substitutionsBuffer) else {
318326
throw ExecuteDistributedTargetError(
319-
message: "Failed to decode distributed target return type")
327+
message: "Failed to decode distributed target return type",
328+
errorCode: .typeDeserializationFailure)
320329
}
321330

322331
guard let resultBuffer = _openExistential(returnTypeFromTypeInfo, do: allocateReturnTypeBuffer) else {
323332
throw ExecuteDistributedTargetError(
324-
message: "Failed to allocate buffer for distributed target return type")
333+
message: "Failed to allocate buffer for distributed target return type",
334+
errorCode: .typeDeserializationFailure)
325335
}
326336

327337
func destroyReturnTypeBuffer<R>(_: R.Type) {
@@ -567,12 +577,49 @@ public protocol DistributedTargetInvocationResultHandler {
567577
@available(SwiftStdlib 5.7, *)
568578
public protocol DistributedActorSystemError: Error {}
569579

580+
/// Error thrown by ``DistributedActorSystem/executeDistributedTarget(on:target:invocationDecoder:handler:)``.
581+
///
582+
/// Inspect the ``errorCode`` for details about the underlying reason this error was thrown.
570583
@available(SwiftStdlib 5.7, *)
571584
public struct ExecuteDistributedTargetError: DistributedActorSystemError {
572-
let message: String
585+
public let errorCode: ErrorCode
586+
public let message: String
587+
588+
public enum ErrorCode {
589+
/// Unable to resolve the target identifier to a function accessor.
590+
/// This can happen when the identifier is corrupt, illegal, or wrong in the
591+
/// sense that the caller and callee do not have the called function recorded
592+
/// using the same identifier.
593+
case targetAccessorNotFound
594+
595+
/// Call target has different number of parameters than arguments
596+
/// provided by the invocation decoder.
597+
case invalidParameterCount
598+
599+
/// Target expects generic environment information, but invocation decoder
600+
/// provided no generic substitutions.
601+
case missingGenericSubstitutions
602+
603+
/// Generic substitutions provided by invocation decoder are incompatible
604+
/// with target of the call. E.g. the generic requirements on the actual
605+
/// target could not be fulfilled by the obtained generic substitutions.
606+
case invalidGenericSubstitutions
607+
608+
// Failed to deserialize type or obtain type information for call.
609+
case typeDeserializationFailure
610+
611+
/// A general issue during the execution of the distributed call target occurred.
612+
case other
613+
}
573614

574615
public init(message: String) {
575616
self.message = message
617+
self.errorCode = .other
618+
}
619+
620+
public init(message: String, errorCode: ErrorCode) {
621+
self.message = message
622+
self.errorCode = errorCode
576623
}
577624
}
578625

stdlib/public/Distributed/DistributedMetadata.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,13 @@ func _getWitnessTablesFor(
108108
environment: UnsafeRawPointer,
109109
genericArguments: UnsafeRawPointer
110110
) -> (UnsafeRawPointer, Int)
111+
112+
@available(SwiftStdlib 5.7, *)
113+
@_silgen_name("swift_distributed_makeDistributedTargetAccessorNotFoundError")
114+
internal // SPI Distributed
115+
func _makeDistributedTargetAccessorNotFoundError() -> Error {
116+
/// We don't include the name of the target in case the input was compromised.
117+
return ExecuteDistributedTargetError(
118+
message: "Failed to locate distributed function accessor",
119+
errorCode: .targetAccessorNotFound)
120+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 main -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 --color
5+
6+
// REQUIRES: executable_test
7+
// REQUIRES: concurrency
8+
// REQUIRES: distributed
9+
10+
// rdar://76038845
11+
// UNSUPPORTED: use_os_stdlib
12+
// UNSUPPORTED: back_deployment_runtime
13+
14+
// FIXME(distributed): Distributed actors currently have some issues on windows, isRemote always returns false. rdar://82593574
15+
// UNSUPPORTED: OS=windows-msvc
16+
17+
import Distributed
18+
import FakeDistributedActorSystems
19+
20+
typealias DefaultDistributedActorSystem = FakeRoundtripActorSystem
21+
22+
distributed actor Greeter {
23+
distributed func greet(name: String) -> String {
24+
"Hello, \(name)!"
25+
}
26+
}
27+
28+
func test() async throws {
29+
let system = DefaultDistributedActorSystem()
30+
let local = Greeter(actorSystem: system)
31+
let ref = try Greeter.resolve(id: local.id, using: system)
32+
33+
// Make sure normal call works ok:
34+
let greeting = try await ref.greet(name: "Caplin")
35+
print("\(greeting)")
36+
// CHECK: Hello, Caplin!
37+
38+
let correctTargetIdentifier = "$s4main7GreeterC5greet4nameS2S_tYaKFTE"
39+
_ = correctTargetIdentifier
40+
let badModuleTargetIdentifier = "$s9BADMODULE7GreeterC5greet4nameS2S_tYaKFTE"
41+
// the BADMODULE is a bad module, and we won't be able to find the distributed accessor
42+
// this should result in a failed call, but not hang the call.
43+
44+
var invocation = Greeter.ActorSystem.InvocationEncoder()
45+
invocation.arguments = ["BadCall"]
46+
invocation.returnType = String.self
47+
48+
let badTarget = RemoteCallTarget(badModuleTargetIdentifier)
49+
50+
do {
51+
// CHECK: >> remoteCall: on:main.Greeter, target:BADMODULE.Greeter.greet(name:)
52+
let call = try await system.remoteCall(
53+
on: local,
54+
target: badTarget,
55+
invocation: &invocation,
56+
throwing: Never.self,
57+
returning: String.self
58+
)
59+
} catch {
60+
// CHECK: << onThrow: ExecuteDistributedTargetError(errorCode: Distributed.ExecuteDistributedTargetError.ErrorCode.targetAccessorNotFound, message: "Failed to locate distributed function accessor")
61+
// CHECK: << remoteCall throw: ExecuteDistributedTargetError(errorCode: Distributed.ExecuteDistributedTargetError.ErrorCode.targetAccessorNotFound, message: "Failed to locate distributed function accessor")
62+
print("caught error: \(error)")
63+
print("call target was: \(badTarget.identifier)")
64+
// CHECK: caught error: ExecuteDistributedTargetError(errorCode: Distributed.ExecuteDistributedTargetError.ErrorCode.targetAccessorNotFound, message: "Failed to locate distributed function accessor")
65+
// CHECK: call target was: $s9BADMODULE7GreeterC5greet4nameS2S_tYaKFTE
66+
}
67+
}
68+
69+
@main struct Main {
70+
static func main() async {
71+
try! await test()
72+
}
73+
}

test/Distributed/Runtime/distributed_actor_remoteCall.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ func test() async throws {
493493
invocationDecoder: &decodeErrDecoder,
494494
handler: FakeResultHandler()
495495
)
496-
// CHECK: ERROR: ExecuteDistributedTargetError(message: "Failed to decode of Int??? (for a test)")
496+
// CHECK: ERROR: ExecuteDistributedTargetError(errorCode: Distributed.ExecuteDistributedTargetError.ErrorCode.other, message: "Failed to decode of Int??? (for a test)")
497497

498498
print("done")
499499
// CHECK-NEXT: done

0 commit comments

Comments
 (0)