-
Notifications
You must be signed in to change notification settings - Fork 79
Rework actor singleton #458
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
4a9e4e4
3bf9aab
e15bc87
a4db9b7
2c23474
91479d6
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 |
|---|---|---|
|
|
@@ -13,35 +13,150 @@ | |
| //===----------------------------------------------------------------------===// | ||
|
|
||
| import DistributedActors | ||
| import DistributedActorsConcurrencyHelpers | ||
|
|
||
| // ==== ---------------------------------------------------------------------------------------------------------------- | ||
| // MARK: Actor singleton plugin | ||
|
|
||
| /// The actor singleton plugin ensures that there is no more than one instance of an actor that is defined to be | ||
| /// singleton running in the cluster. | ||
| /// | ||
| /// An actor singleton may run on any node in the cluster. Use `ActorSingletonSettings.allocationStrategy` to control | ||
| /// its allocation. On candidate nodes where the singleton might run, use `ActorSystem.singleton.ref(type:name:props:behavior)` | ||
| /// to define actor behavior. Otherwise, call `ActorSystem.singleton.ref(type:name:)` to obtain a ref. The returned | ||
| /// `ActorRef` is in reality a proxy which handle situations where the singleton is shifted to different nodes. | ||
| /// | ||
| /// - Warning: Refer to the configured `AllocationStrategy` for trade-offs between safety and recovery latency for | ||
| /// the singleton allocation. | ||
| /// - SeeAlso: The `ActorSingleton` mechanism is conceptually similar to Erlang/OTP's <a href="http://erlang.org/doc/design_principles/distributed_applications.html">`DistributedApplication`</a>, | ||
| /// and <a href="https://doc.akka.io/docs/akka/current/cluster-singleton.html">`ClusterSingleton` in Akka</a>. | ||
| public final class ActorSingletonPlugin { | ||
| private var singletons: [String: BoxedActorSingleton] = [:] | ||
|
Member
Author
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. Worry about this. This is mutable. Safe?
Member
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. Forgot to comment here -- yes this is not safe indeed... We need to put a lock around accessing these and host() calls etc...
Member
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. How I'd really want to write all this is (but it's too annoying I think, until we have async/await things): since then all of the mutable state can actually be put inside an actor, and we'd implement by "asking the plugin actor about the ref" rather than doing it here where we are not safe from concurrent access... We could do this today but it gets too annoying, since it'd be like
Member
Author
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. Do we use NIO's lock in this project or something else?
Member
Author
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. |
||
| private let singletonsLock = Lock() | ||
|
|
||
| public init() {} | ||
|
|
||
| func ref<Message>(of type: Message.Type, settings: ActorSingletonSettings, system: ActorSystem, props: Props? = nil, _ behavior: Behavior<Message>? = nil) throws -> ActorRef<Message> { | ||
yim-lee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| try self.singletonsLock.withLock { | ||
| if let existing = self.singletons[settings.name] { | ||
| guard let proxy = existing.unsafeUnwrapAs(Message.self).proxy else { | ||
| fatalError("Singleton [\(settings.name)] not yet initialized") | ||
| } | ||
| return proxy | ||
| } | ||
|
|
||
| let singleton = ActorSingleton<Message>(settings: settings, props: props, behavior) | ||
| try singleton.spawnAll(system) | ||
| self.singletons[settings.name] = BoxedActorSingleton(singleton) | ||
|
|
||
| guard let proxy = singleton.proxy else { | ||
| fatalError("Singleton[\(settings.name)] not yet initialized") | ||
| } | ||
|
|
||
| return proxy | ||
| } | ||
| } | ||
|
|
||
| func actor<Act: Actorable>(of type: Act.Type, settings: ActorSingletonSettings, system: ActorSystem, props: Props? = nil, _ makeInstance: ((Actor<Act>.Context) -> Act)? = nil) throws -> Actor<Act> { | ||
| let behavior = makeInstance.map { maker in | ||
| Behavior<Act.Message>.setup { context in | ||
| Act.makeBehavior(instance: maker(.init(underlying: context))) | ||
| } | ||
| } | ||
| let ref = try self.ref(of: Act.Message.self, settings: settings, system: system, behavior) | ||
| return Actor<Act>(ref: ref) | ||
| } | ||
| } | ||
|
|
||
| extension ActorSingletonPlugin { | ||
| func ref<Message>(of type: Message.Type, name: String, system: ActorSystem, props: Props? = nil, _ behavior: Behavior<Message>? = nil) throws -> ActorRef<Message> { | ||
| let settings = ActorSingletonSettings(name: name) | ||
| return try self.ref(of: type, settings: settings, system: system, props: props, behavior) | ||
| } | ||
|
|
||
| func actor<Act: Actorable>(of type: Act.Type, name: String, system: ActorSystem, props: Props? = nil, _ makeInstance: ((Actor<Act>.Context) -> Act)? = nil) throws -> Actor<Act> { | ||
ktoso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let settings = ActorSingletonSettings(name: name) | ||
| return try self.actor(of: type, settings: settings, system: system, props: props, makeInstance) | ||
| } | ||
| } | ||
|
|
||
| // ==== ---------------------------------------------------------------------------------------------------------------- | ||
| // MARK: Plugin protocol conformance | ||
|
|
||
| extension ActorSingletonPlugin: Plugin { | ||
| static let pluginKey = PluginKey<ActorSingletonPlugin>(plugin: "$actorSingleton") | ||
|
|
||
| public var key: Key { | ||
| Self.pluginKey | ||
| } | ||
|
|
||
| public func start(_ system: ActorSystem) -> Result<Void, Error> { | ||
| .success(()) | ||
| } | ||
|
|
||
| // TODO: Future | ||
| public func stop(_ system: ActorSystem) -> Result<Void, Error> { | ||
| self.singletonsLock.withLock { | ||
| for (_, singleton) in self.singletons { | ||
| singleton.stop(system) | ||
| } | ||
| } | ||
| return .success(()) | ||
| } | ||
| } | ||
|
|
||
| // ==== ---------------------------------------------------------------------------------------------------------------- | ||
| // MARK: Singleton refs and actors | ||
|
|
||
| extension ActorSystem { | ||
| public var singleton: ActorSingletonLookup { | ||
| public var singleton: ActorSingletonControl { | ||
| .init(self) | ||
| } | ||
| } | ||
|
|
||
| /// Allows for simplified lookups of actor references which are known to be managed by `ActorSingleton`. | ||
| public struct ActorSingletonLookup { | ||
| /// Provides actor singleton controls such as obtaining a singleton ref and defining the singleton. | ||
| public struct ActorSingletonControl { | ||
| private let system: ActorSystem | ||
|
|
||
| internal init(_ system: ActorSystem) { | ||
| self.system = system | ||
| } | ||
|
|
||
| /// Obtains a reference to a (proxy) singleton regardless of its current location. | ||
| public func ref<Message>(name: String, of type: Message.Type) throws -> ActorRef<Message> { | ||
| let key = ActorSingleton<Message>.pluginKey(name: name) | ||
| guard let singleton = self.system.settings.plugins[key] else { | ||
| private var singletonPlugin: ActorSingletonPlugin { | ||
| let key = ActorSingletonPlugin.pluginKey | ||
| guard let singletonPlugin = self.system.settings.plugins[key] else { | ||
| fatalError("No plugin found for key: [\(key)], installed plugins: \(self.system.settings.plugins)") | ||
| } | ||
| guard let proxy = singleton.proxy else { | ||
| fatalError("Singleton[\(key)] not yet initialized") | ||
| } | ||
| return proxy // FIXME: Worried that we never synchronize access to proxy... | ||
| return singletonPlugin | ||
| } | ||
|
|
||
yim-lee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| public func actor<Act: Actorable>(name: String, _ type: Act.Type) throws -> Actor<Act> { | ||
| let ref = try self.ref(name: name, of: Act.Message.self) | ||
| return Actor<Act>(ref: ref) | ||
| /// Defines a singleton `behavior` and indicates that it can be hosted on this node. | ||
| public func host<Message>(_ type: Message.Type, name: String, props: Props = Props(), _ behavior: Behavior<Message>) throws -> ActorRef<Message> { | ||
| try self.singletonPlugin.ref(of: type, name: name, system: self.system, props: props, behavior) | ||
| } | ||
|
|
||
| /// Defines a singleton `behavior` and indicates that it can be hosted on this node. | ||
| public func host<Message>(_ type: Message.Type, settings: ActorSingletonSettings, props: Props = Props(), _ behavior: Behavior<Message>) throws -> ActorRef<Message> { | ||
| try self.singletonPlugin.ref(of: type, settings: settings, system: self.system, props: props, behavior) | ||
| } | ||
|
|
||
| /// Defines a singleton `Actorable` and indicates that it can be hosted on this node. | ||
| public func host<Act: Actorable>(_ type: Act.Type, name: String, props: Props = Props(), _ makeInstance: @escaping (Actor<Act>.Context) -> Act) throws -> Actor<Act> { | ||
| try self.singletonPlugin.actor(of: type, name: name, system: self.system, props: props, makeInstance) | ||
| } | ||
|
|
||
| /// Defines a singleton `Actorable` and indicates that it can be hosted on this node. | ||
| public func host<Act: Actorable>(_ type: Act.Type, settings: ActorSingletonSettings, props: Props = Props(), _ makeInstance: @escaping (Actor<Act>.Context) -> Act) throws -> Actor<Act> { | ||
| try self.singletonPlugin.actor(of: type, settings: settings, system: self.system, props: props, makeInstance) | ||
| } | ||
|
|
||
| /// Obtains a ref to the specified actor singleton. | ||
| public func ref<Message>(of type: Message.Type, name: String) throws -> ActorRef<Message> { | ||
| try self.singletonPlugin.ref(of: type, name: name, system: self.system) | ||
| } | ||
|
|
||
| /// Obtains the specified singleton actor. | ||
| public func actor<Act: Actorable>(of type: Act.Type, name: String) throws -> Actor<Act> { | ||
| try self.singletonPlugin.actor(of: type, name: name, system: self.system) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.