-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Make the actor transport an associated type of the DistributedActor protocol #39985
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,8 +61,8 @@ static FuncDecl *deriveDistributedActor_resolve(DerivedConformance &derived) { | |
return param; | ||
}; | ||
|
||
Type addressType = C.getAnyActorIdentityDecl()->getDeclaredInterfaceType(); | ||
Type transportType = getDistributedActorTransportType(decl); | ||
auto addressType = C.getAnyActorIdentityDecl()->getDeclaredInterfaceType(); | ||
auto transportType = getDistributedActorTransportType(decl); | ||
|
||
// (_ identity: AnyActorIdentity, using transport: ActorTransport) | ||
auto *params = ParameterList::create( | ||
|
@@ -133,10 +133,11 @@ static ValueDecl *deriveDistributedActor_actorTransport( | |
|
||
// ``` | ||
// nonisolated | ||
// let actorTransport: ActorTransport | ||
// let actorTransport: Transport | ||
// ``` | ||
// (no need for @actorIndependent because it is an immutable let) | ||
Type propertyType = getDistributedActorTransportType(derived.Nominal); | ||
auto propertyType = getDistributedActorTransportType(derived.Nominal); | ||
|
||
VarDecl *propDecl; | ||
PatternBindingDecl *pbDecl; | ||
std::tie(propDecl, pbDecl) = derived.declareDerivedProperty( | ||
|
@@ -154,6 +155,36 @@ static ValueDecl *deriveDistributedActor_actorTransport( | |
return propDecl; | ||
} | ||
|
||
static Type deriveDistributedActor_Transport( | ||
DerivedConformance &derived) { | ||
assert(derived.Nominal->isDistributedActor()); | ||
auto &C = derived.Context; | ||
|
||
// Look for a type DefaultActorTransport within the parent context. | ||
auto defaultTransportLookup = TypeChecker::lookupUnqualified( | ||
derived.getConformanceContext()->getModuleScopeContext(), | ||
DeclNameRef(C.Id_DefaultActorTransport), | ||
derived.ConformanceDecl->getLoc()); | ||
TypeDecl *defaultTransportTypeDecl = nullptr; | ||
for (const auto &found : defaultTransportLookup) { | ||
if (auto foundType = dyn_cast_or_null<TypeDecl>(found.getValueDecl())) { | ||
if (defaultTransportTypeDecl) { | ||
// Note: ambiguity, for now just fail. | ||
return nullptr; | ||
} | ||
|
||
defaultTransportTypeDecl = foundType; | ||
continue; | ||
} | ||
} | ||
|
||
// There is no default, so fail to synthesize. | ||
if (!defaultTransportTypeDecl) | ||
return nullptr; | ||
|
||
// Return the default transport type. | ||
return defaultTransportTypeDecl->getDeclaredInterfaceType(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So... this means that we look in the current module for // no default transport
distributed actor X {} // error: no DefaultActorTransport, provide `typealias Transport` pls typealias DefaultActorTransport = ClusterTransport // example
distributed actor X {} // ok
X(transport: SomeOtherTransport()) // error, expected ClusterTransport right? This piece I like, that makes sense for applications which will usually have one transport. I was hoping we can derive it from the init declarations: distributed actor A {
init(t: MyTransport) {}
// therefore:
typealias Transport = MyTransport
} and also once all transport types must "match": distributed actor A {
init(t: MyTransport) {}
init(number: Int, other: OtherTransport) {}
// ERROR: distributed actor initializers must accept
// the same transport type in all initializers
The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I've noted in my newly-written comment, I expect that any library providing a transport would provide a |
||
// ==== ------------------------------------------------------------------------ | ||
|
||
ValueDecl *DerivedConformance::deriveDistributedActor(ValueDecl *requirement) { | ||
|
@@ -174,3 +205,17 @@ ValueDecl *DerivedConformance::deriveDistributedActor(ValueDecl *requirement) { | |
|
||
return nullptr; | ||
} | ||
|
||
std::pair<Type, TypeDecl *> DerivedConformance::deriveDistributedActor( | ||
AssociatedTypeDecl *assocType) { | ||
if (!canDeriveDistributedActor(Nominal, cast<DeclContext>(ConformanceDecl))) | ||
return std::make_pair(Type(), nullptr); | ||
|
||
if (assocType->getName() == Context.Id_Transport) { | ||
return std::make_pair(deriveDistributedActor_Transport(*this), nullptr); | ||
} | ||
|
||
Context.Diags.diagnose(assocType->getLoc(), | ||
diag::broken_distributed_actor_requirement); | ||
return std::make_pair(Type(), nullptr); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,9 @@ public protocol AnyActor: Sendable, AnyObject {} | |
@available(SwiftStdlib 5.6, *) | ||
public protocol DistributedActor: | ||
AnyActor, Sendable, Identifiable, Hashable, Codable { | ||
/// The type of transport used to communicate with actors of this type. | ||
associatedtype Transport: ActorTransport | ||
ktoso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// Resolves the passed in `identity` against the `transport`, returning | ||
/// either a local or remote actor reference. | ||
/// | ||
|
@@ -52,7 +55,7 @@ public protocol DistributedActor: | |
// We want to move to accepting a generic or existential identity here | ||
// static func resolve<Identity>(_ identity: Identity, using transport: ActorTransport) | ||
// throws -> Self where Identity: ActorIdentity | ||
static func resolve(_ identity: AnyActorIdentity, using transport: ActorTransport) | ||
static func resolve(_ identity: AnyActorIdentity, using transport: Transport) | ||
throws -> Self | ||
|
||
/// The `ActorTransport` associated with this actor. | ||
|
@@ -61,7 +64,7 @@ public protocol DistributedActor: | |
/// | ||
/// Conformance to this requirement is synthesized automatically for any | ||
/// `distributed actor` declaration. | ||
nonisolated var actorTransport: ActorTransport { get } // TODO: rename to `transport`? | ||
nonisolated var actorTransport: Transport { get } // TODO: rename to `transport`? | ||
|
||
/// Logical identity of this distributed actor. | ||
/// | ||
|
@@ -100,9 +103,9 @@ extension CodingUserInfoKey { | |
@available(SwiftStdlib 5.6, *) | ||
extension DistributedActor { | ||
nonisolated public init(from decoder: Decoder) throws { | ||
guard let transport = decoder.userInfo[.actorTransportKey] as? ActorTransport else { | ||
guard let transport = decoder.userInfo[.actorTransportKey] as? Transport else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm... I see, this is more restrictive but I think it'll be fine -- realistically people should not be mixing completely random transports and sending an actor from one transport to another transport, I suppose... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, that's a good point, this might be a bug! We probably need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nah, transports are not intended to be Codable; This change seems fine. I was thinking about a weird/obscure use-case that to be honest we don't have to support IMHO: "mixing transports". It would be very complicated and I don't think we really need it in any real use-case to be honest. Here is the imagined problem:
To be honest this is likely good, you can't just randomly share refs like that as the connections and forwarding take a role in all that too... I think this is okey to keep as such runtime `"failed to decode websocket:/// actor using Cluster transport" it makes total sense and is easily fixed by developers with a "forwarder" actor if they need such chain of messages. Why all of this doesn't really matter:
So yeah, such weird thing could happen, but honestly I think it's a weird edge case, and by means of our transports being explicit in the types -- we'd see that "huh, why am I sending this websocket actor to the cluster, does this make sense?" One can spin up a simple "forwarder" actor if we needed to support that. |
||
throw DistributedActorCodingError(message: | ||
"Missing ActorTransport (for key .actorTransportKey) " + | ||
"Missing Transport (for key .actorTransportKey) " + | ||
"in Decoder.userInfo, while decoding \(Self.self).") | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just a left-over from debugging?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoop, I'll remove that in a follow up