-
Notifications
You must be signed in to change notification settings - Fork 157
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
[WIP] Add Distributed module documentation #326
base: main
Are you sure you want to change the base?
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.
Mainly puts comments on the last section around Emulating callbacks as I found some errors in code examples
| distributed func call(later: InfoListener) async -> String { | ||
| Task.detached { | ||
| // do some asynchronous processing AFTER returning from the 'call' method... | ||
| later("Here's the info you asked for!") |
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.
later is not a closure anymore and is async throws
| later("Here's the info you asked for!") | |
| try await later.additionalInfo("Here's the info you asked for!") |
| In distributed actors, the same can be achieved using distributed method calls rather than closures, like this: | ||
|
|
||
| ```swift | ||
| protocol InfoListener: Codable { |
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.
| protocol InfoListener: Codable { | |
| protocol InfoListener: DistributedActor, Codable { |
|
|
||
| ```swift | ||
| protocol InfoListener: Codable { | ||
| func additionalInfo(_ info: String) async throws |
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.
| func additionalInfo(_ info: String) async throws | |
| distributed func additionalInfo(_ info: String) |
| } | ||
|
|
||
| distributed actor Alice { | ||
| distributed func call(later: InfoListener) async -> String { |
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.
| distributed func call(later: InfoListener) async -> String { | |
| distributed func call(later: some InfoListener) async -> String { |
In this case Later concrete type must be known on Alice side.
| this protocol: | ||
|
|
||
| ```swift | ||
| distributed actor Bob { |
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.
| distributed actor Bob { | |
| distributed actor Bob: Codable { |
| we registered a closure to be called at some later point in time when some additional information was looked up by the | ||
| actor. In local-only actors, a common way of modeling this is passing a closure to the actor. | ||
|
|
||
| In distributed actors, the same can be achieved using distributed method calls rather than closures, like this: |
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.
We should explain that Alice and Bob must share the same ActorSystem. Also there are two cases: is Bob type known on Alice side or not
|
|
||
| extension Bob: InfoListener { | ||
| distributed func additionalInfo(_ info: String) { | ||
| print("later: \(laterReply)") |
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.
| print("later: \(laterReply)") | |
| print("later: \(info)") |
| print("later: \(laterReply)") | ||
| } | ||
| } | ||
|
|
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.
Using resolvable protocol we could have something like that:
@Resolvable
protocol InfoListener: DistributedActor, Codable {
distributed func additionalInfo(_ info: String)
}
distributed actor Alice {
distributed func call(later: $InfoListener) async -> String {
Task.detached {
// do some asynchronous processing AFTER returning from the 'call' method...
try await later.additionalInfo("Here's the info you asked for!")
}
return "Thanks for your call!"
}
}
distributed actor Bob: DefaultDistributedActorSystem.SerializationRequirement {
func test(alice: Alice) async throws {
let infoListener = try $InfoListener.resolve(id: self.id, using: self.actorSystem)
let immediateReply = try await alice.call(later: infoListener)
print("immediately: \(immediateReply)")
}
}
extension Bob: InfoListener {
distributed func additionalInfo(_ info: String) {
print("later: \(info)")
}
}| ```swift | ||
| distributed actor Bob { | ||
| func test(alice: Alice) async { | ||
| let immediateReply = try await alice.call(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.
| let immediateReply = try await alice.call(self) | |
| // We have to make sure, in some way, that the `DistributedCallback` instance stays alive to receive the callback | |
| let immediateReply = try await alice.call(later: DistributedCallback(name: "Bob", actorSystem: self.actorSystem)) |
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.
Overall like the page, gives a quick recap of the module!
Want to reread it a bit later with a fresh mind, but at the moment have two small comments. Added them as suggestions, but take them with a grain of salt.
- Overall I think would be nice to add some small sentence on why exactly one should use
distributedmodule. - Think memory management could also be highlighted—it's not quite obvious at a first glance. Though not sure about placing, it's quite specific. Maybe even be placed in
Implementing Your Own DistributedActorSystemsection as it's mostly for systems to care about?
|
|
||
| ## Thinking in Distributed Actors | ||
|
|
||
| In order to build distributed systems successfully you will need to get into the right mindset. |
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.
| In order to build distributed systems successfully you will need to get into the right mindset. | |
| Building distributed systems is a complex task that requires careful consideration of reliability, maintainability, and scalability. There are various approaches to build such systems, and Swift, as a general-purpose language, offers tools for this, such as actors. However, in order to build distributed systems successfully with this tools, you will need to get into the right mindset. |
| The actor system may return a local or remote reference, however it should not perform asynchronous work such as | ||
| trying to confirm if the actor exists remotely or not. The resolve method should quickly return either a known | ||
| local actor identified by the passed `id` or a remote reference if the actor may exist remotely. | ||
|
|
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.
| > Note: Be careful with memory management. Local actors already stored on the node, so it might be better to keep references to them as weak. However, for remote actors reference is generated, which could potentially be cleaned if not kept strongly. // TODO: Link to Automatic Reference Counting page? | |
| As we can see, an cross-actor call to an actor function caused an implicit `async` effect, | ||
| while the same type of call to a `distributed func` caused an additional `throws` effect that needed to be handled | ||
| with a `try`. Since the method `increment(by:)` itself is not declared throwing by itself, we know that the only | ||
| way this method can fail, is by the underlying transport mechanism failing in sme way. |
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.
| way this method can fail, is by the underlying transport mechanism failing in sme way. | |
| way this method can fail, is by the underlying transport mechanism failing in some way. |
This is a very early version of
Distributedmodule documentation; It is not polished up, may have stray sentences and sections etc.We'll figure out how to best present the information through the pitch posted on the forums over here:
I'll also work on improving tone and voice still, right now it is not the most consistent.
For now, we can discuss what needs to be explained in this documentation and I started a forums thread for this purpose: TSPL Pitch: Distributed.