[Proposal] Distributed Actor Isolation

Yes, that sounds right. This feels a lot like SE-0313, where we took a lexical property (self in a method is isolated) and generalized it to be part of the type system with isolated parameters.

Just a few comments on revision 1.3 while I'm here:

Stored properties cannot ever be access from outside the distributed actor. They cannot be declared distributed nor nonisolated.

Per the discussion above, we don't have to be this restrictive. We cannot access stored properties from another node, but we could access them from outside the actor if we know we're still on the same node. Here's a silly example:

distributed actor Counter {
  var count = 0
  func publishNextValue() {
    count += 1
    Task.detached { @MainActor in
       ui.countLabel.text = "Count is now \(await self.count)"
     }
  }
}

Because we're in a closure defined in a lexical context where self was isolated, we know that self is on the local node, so it's okay to read one of its stored properties (asynchronously).

I think the restriction you want is that stored properties cannot be accessed if the actor isn't known to be local. Or, you can write this in terms of the more general rule governing the addition of effects: if we would need to add the throws effect to read the stored property, then the access is ill-formed.

Thanks for adding the DistributedActor protocol! A couple of minor things:

  • The associated type name DistributedActorSystem is really long. How about just ActorSystem here (since we're already in the DistributedActor protocol), and then DistributedActorSystem can remain the name of the protocol?
  • The type alias Identity plays exactly the same role as the type ID from the Identifiable protocol. Should we use ID here everywhere rather than having two names for the same thing?
  • The property id should have type Identity (or ID if you take my suggestion immediately prior to this), not ActorIdentity.

For the DistributedActorSystemProtocol, a few comments:

  • As noted above, I think this should be named DistributedActorSystem)
  • You can default SerializationRequirement to Codable in the definition, e.g., associatedtype SerializationRequirement = Codable

The standard library provides a type-eraser called AnyDistributedActorSystem for this purpose, and distributed actors default to this type of actor system.

While I can see why AnyDistributedActorSystem can be useful (e.g., to decide between different actor system implementations dynamically), I don't think we should use it as a default, because it pushes folks toward a less-efficient, type-erased implementation. Rather, I'd expect each library to provide a DefaultDistributedActorSystem so there is a good default tailored to the libraries you use. One can then opt in to type erasure.

As an aside, AnyDistributedActorSystem either needs to be given a complete API or its mention should be removed.

This...

protocol ClusterActor: DistributedActor {
  typealias DistributedActorSystem = ClusterSystem
}

would be better expressed as

protocol ClusterActor: DistributedActor 
  where Self.DistributedActorSystem == ClusterSystem {
}

A distributed actor's designated initializer must always contain exactly one DistributedActorSystem parameter. This is because the lifecycle and messaging of a distributed actor is managed by the system.

Hmm. Should we recognize this parameter because it has the same type as DistributedActorSystem, or should we also require the argument name to match (e.g., it must be called actorSystem)? I have a vague preference for the latter, because it's easier to syntactically match, and type identity is not always an obvious thing... one could turn an Int into a distributed actor system and make it very, very annoying write initializers that work.

I don't think I understand why we're making the designated vs. convenience initializer distinction here, especially when part of the discussion of SE-0327 is about eliminating this distinction.

If a distributed actor's Identity conforms to Codable, the distributed actor automatically gains a Codable conformance as well.

I'm struggling a bit to understand how this specific use of Codable relates to SerializationRequirement. Should it be the case that a distributed actor should implicitly conform to the protocols in SerializationRequirement whenever its Identity type conforms to the protocols in SerializationRequirement? The actual implementation of the requirements in those protocols might end up being synthesized (they will for Codable) or could come from some implementations in a protocol extension. Is that the general rule here, or is Codable special for some reason?

Doug

1 Like