https://kotlinlang.org logo
#coroutines
Title
# coroutines
j

Jonathan

10/10/2018, 9:17 AM
I tried to achieve "well-designed" actor system. But in my experimentations I found very difficult to compose actors without using the ask & wait anti-pattern. It is possible, but it quickly leads overcomplicated code IMHO. Do you have good resources where I could learn how to nicely compose actors without ask & wait?
e

enleur

10/10/2018, 9:19 AM
what do you mean “ask and wait”? can you show code examples?
Another example, using explicit message types:
Copy code
sealed class Message
data class Ask(val question: Question, val response: CompletableDeferred<Response>)

suspend fun usage() {
  val response = CompletableDeferred<Response>()
  
  actor.send(Ask(Question(), response)) // ask
  val result = response.await() // wait
  
  // use the result
}
d

dave08

10/10/2018, 10:13 AM
You might be able to copy the new actor draft from the branch in the repo... but then I suppose you probably saw that too? https://github.com/Kotlin/kotlinx.coroutines/tree/actors-prototype
I personally think there is a
actWithResult { }
pattern missing there... but on one discussion with @[removed] it might not be so difficult to implement. I think the use case is running using a regular
act { }
and getting results in another context using
actWithResult { }
, or simply having a background context to call
actWithResult { }
, the actor would insure that any requests will be handled synchronously...
j

Jonathan

10/10/2018, 10:23 AM
Indeed it is easy to write a
actAndReply
suspending function on top of
act
. The problem is more about best and bad practices. @[removed] and @elizarov said that such pattern should be discouraged by kotlinx.coroutines, because it is an anti pattern (https://github.com/Kotlin/kotlinx.coroutines/pull/485#discussion_r208939367). So my question is more: How one can write simple actor composition and avoid this ant-pattern?
d

dave08

10/10/2018, 10:23 AM
Is that what you're looking for @Jonathan?
j

Jonathan

10/10/2018, 10:29 AM
@dave08 Not really sorry. I knew all that, and I did implement my own version of the abstract actor with an
act
and
actAndReply
in order to play with it. The thing is, since the ask & wait is considered an anti-pattern, I'd like to avoid it. But I struggle to compose actors without it.
d

dave08

10/10/2018, 10:32 AM
Maybe have
actAndReply
return
Deferred
?
That way we avoid the extra param passing... @Jonathan?
j

Jonathan

10/10/2018, 10:37 AM
hmmm... Usually if a method returns a deferred it is better to simply makes the method suspend.
suspend fun foo(): Result
is equivalent and should be preferred to
fun foo(): Deferred<Result>
.
And the thing is in both cases, it would still be an ask & wait which is what I want to avoid.
e

elizarov

10/10/2018, 10:38 AM
You avoid it by structuring your code as data processing pipe-line, instead of a regular request-response.
j

Jonathan

10/10/2018, 10:38 AM
The answer is probably in the actor-model itself, which is then not really related to Kotlin. But after reading many articles and blog (usually with examples in Scala) I still don't know.
e

elizarov

10/10/2018, 10:39 AM
It scales better and it just leads to saner code
j

Jonathan

10/10/2018, 10:39 AM
Yes. I got that. Now the question is how to compose actors?
(I can do it, but it quickly becomes complicated without the ask&wait)
d

dave08

10/10/2018, 10:40 AM
Right, but at least the user opts into when they block on the call just like CompletableDeferred param passing... which is currently recommended in the docs... @elizarov That's exactly what we did, but I found myself with tons of side channels for progress notification etc... is that what you mean by composing @Jonathan?
So it ends up not being a pipeline anymore...
e

elizarov

10/10/2018, 10:42 AM
If you need ask&wait, then you’ll like not need actors in that places at allo.
d

dave08

10/10/2018, 10:42 AM
It's all one resource being used by multiple services, and it needs to report its state in different ways in my case...
e

elizarov

10/10/2018, 10:42 AM
Just write regular functions, top-level, pure functions
j

Jonathan

10/10/2018, 10:43 AM
Is actor-composition a good practice?
e

elizarov

10/10/2018, 10:44 AM
What is actor-composition? And actor is just than thing on the other side of a channels that keeps some state for you. You usually pipe-line them (one actor does something, sends something down the pipelines, etc…). That’s how you build a system out of them
Sometimes you need to get back data back from an actor. But you should not wait. You should give your channel to that actor and let it send of that channel, so that you can process responses asynchronousy.
j

Jonathan

10/10/2018, 10:48 AM
Yes, and that's what I did. But then I ended with complicated code. Because when I receive a message from an actor to which I delegated work, I have to retrieve the context of the request in order to know what to do with that message.
Maybe actor is simply not the good abstraction for composition then.
(By actor composition I meant creating a (composed) actor, which decompose and delegate its work to other (component) actors)
e

elizarov

10/10/2018, 11:11 AM
You “compose” actor if and only if you need to scale them differently. (have X copies for actor doing one piece of work and Y copies for actor doing antoher piece of work)
If you don’t need that, you don’t need actor. You use plain suspending functions
j

Jonathan

10/10/2018, 11:28 AM
Yes, I was playing in this context. I tried to create an actor delegating smaller piece of work to x copies of sub-actor A and y copies of sub-actor B.
e

elizarov

10/10/2018, 11:29 AM
In this case they have to use channels to communicate back-and-forth. If they ask & wait, it will not scale the way you want to.
j

Jonathan

10/10/2018, 11:29 AM
Maybe my mistake is to use actor to perform work? I should only use them to maintain state? Therefore the whole concept of decomposing work would become meaningless.
e

elizarov

10/10/2018, 11:55 AM
You use actors to maintain state. You use worker pool (a set of N coroutines) to perform concurrency-limited work (we don’t have nice wrapper for that pattern yet, but will do in a future)
👍 4
j

Jonathan

10/10/2018, 12:20 PM
Thanks for your enlightenments @elizarov.
4 Views