-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Introduce a builtin and API for getting the local actor from a distributed one #71043
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
Introduce a builtin and API for getting the local actor from a distributed one #71043
Conversation
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.
Looking great so far, thank you Doug!
@_implements is in this odd state where it is dropped from serialization, but persists everywhere else. Keep it in the serialized modules.
…buted one When an actual instance of a distributed actor is on the local node, it is has the capabilities of `Actor`. This isn't expressible directly in the type system, because not all `DistributedActor`s are `Actor`s, nor is the opposite true. Instead, provide an API `DistributedActor.asLocalActor` that can only be executed when the distributed actor is known to be local (because this API is not itself `distributed`), and produces an existential `any Actor` referencing that actor. The resulting existential value carries with it a special witness table that adapts any type conforming to the DistributedActor protocol into a type that conforms to the Actor protocol. It is "as if" one had written something like this: extension DistributedActor: Actor { } which, of course, is not permitted in the language. Nonetheless, we lovingly craft such a witness table: * The "type" being extended is represented as an extension context, rather than as a type context. This hasn't been done before, all Swift runtimes support it uniformly. * A special witness is provided in the Distributed library to implement the `Actor.unownedExecutor` operation. This witness back-deploys to the Swift version were distributed actors were introduced (5.7). On Swift 5.9 runtimes (and newer), it will use `DistributedActor.unownedExecutor` to support custom executors. * The conformance of `Self: DistributedActor` is represented as a conditional requirement, which gets satisfied by the witness table that makes the type a `DistributedActor`. This makes the special witness work. * The witness table is *not* visible via any of the normal runtime lookup tables, because doing so would allow any `DistributedActor`-conforming type to conform to `Actor`, which would break the safety model. * The witness table is emitted on demand in any client that needs it. In back-deployment configurations, there may be several witness tables for the same concrete distributed actor conforming to `Actor`. However, this duplication can only be observed under fairly extreme circumstances (where one is opening the returned existential and instantiating generic types with the distributed actor type as an `Actor`, then performing dynamic type equivalence checks), and will not be present with a new Swift runtime. All of these tricks together mean that we need no runtime changes, and `asLocalActor` back-deploys as far as distributed actors, allowing it's use in `#isolation` and the async for...in loop.
6004e29
to
97ea19d
Compare
@swift-ci please test |
@swift-ci please test |
Windows build timed out... retrying |
@swift-ci please test Windows |
There's some icky code in here I still need to clean up, but I'm merging so this can be picked up for |
protocol-to-protocol conformances, such as the DistributedActor-to-Actor conformance which was introduced in swiftlang#71043. FIXME: this is not a good solution, working towards a real one
protocol-to-protocol conformances, such as the DistributedActor-to-Actor conformance which was introduced in swiftlang#71043. FIXME: this is not a good solution, working towards a real one
When an actual instance of a distributed actor is on the local node, it is
has the capabilities of
Actor
. This isn't expressible directly in the typesystem, because not all
DistributedActor
s areActor
s, nor is theopposite true.
Instead, provide an API
DistributedActor.asLocalActor
that can onlybe executed when the distributed actor is known to be local (because
this API is not itself
distributed
), and produces an existentialany Actor
referencing that actor. The resulting existential valuecarries with it a special witness table that adapts any type
conforming to the DistributedActor protocol into a type that conforms
to the Actor protocol. It is "as if" one had written something like this:
which, of course, is not permitted in the language. Nonetheless, we
lovingly craft such a witness table:
The "type" being extended is represented as an extension context,
rather than as a type context. This hasn't been done before, all Swift
runtimes support it uniformly.
A special witness is provided in the Distributed library to implement
the
Actor.unownedExecutor
operation. This witness back-deploys to theSwift version were distributed actors were introduced (5.7). On Swift
5.9 runtimes (and newer), it will use
DistributedActor.unownedExecutor
to support custom executors.The conformance of
Self: DistributedActor
is represented as aconditional requirement, which gets satisfied by the witness table
that makes the type a
DistributedActor
. This makes the specialwitness work.
The witness table is not visible via any of the normal runtime
lookup tables, because doing so would allow any
DistributedActor
-conforming type to conform toActor
, which wouldbreak the safety model.
The witness table is emitted on demand in any client that needs it.
In back-deployment configurations, there may be several witness tables
for the same concrete distributed actor conforming to
Actor
.However, this duplication can only be observed under fairly extreme
circumstances (where one is opening the returned existential and
instantiating generic types with the distributed actor type as an
Actor
, then performing dynamic type equivalence checks), and willnot be present with a new Swift runtime.
All of these tricks together mean that we need no runtime changes, and
asLocalActor
back-deploys as far as distributed actors, allowing it'suse in
#isolation
and the async for...in loop.