Skip to content

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

Merged
merged 3 commits into from
Jan 23, 2024

Conversation

DougGregor
Copy link
Member

@DougGregor DougGregor commented Jan 21, 2024

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 DistributedActors are Actors, 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.

Copy link
Contributor

@ktoso ktoso left a 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!

@ktoso ktoso added the distributed Feature → concurrency: distributed actor label Jan 21, 2024
@_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.
@DougGregor DougGregor force-pushed the distributed-actor-to-actor branch from 6004e29 to 97ea19d Compare January 23, 2024 01:28
@DougGregor
Copy link
Member Author

@swift-ci please test

@DougGregor
Copy link
Member Author

@swift-ci please test

@DougGregor DougGregor changed the title Distributed actor to actor @DougGregor Introduce a builtin and API for getting the local actor from a distributed one Jan 23, 2024
@DougGregor DougGregor changed the title @DougGregor Introduce a builtin and API for getting the local actor from a distributed one Introduce a builtin and API for getting the local actor from a distributed one Jan 23, 2024
@DougGregor
Copy link
Member Author

Windows build timed out... retrying

@DougGregor
Copy link
Member Author

@swift-ci please test Windows

@DougGregor DougGregor marked this pull request as ready for review January 23, 2024 21:01
@DougGregor DougGregor merged commit 8c448a5 into swiftlang:main Jan 23, 2024
@DougGregor DougGregor deleted the distributed-actor-to-actor branch January 23, 2024 21:02
@DougGregor
Copy link
Member Author

There's some icky code in here I still need to clean up, but I'm merging so this can be picked up for #isolation and async sequence work.

ktoso added a commit to ktoso/swift that referenced this pull request May 14, 2024
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
ktoso added a commit to ktoso/swift that referenced this pull request May 20, 2024
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
distributed Feature → concurrency: distributed actor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants