I have an actor, that delivers messages to 4 sub a...
# coroutines
g
I have an actor, that delivers messages to 4 sub actors. How can I ensure synchronisation? Why do I have sub actors? To avoid deadlocking when needing to call a different internal method, This is the code is anyones curious https://github.com/ghosalmartin/AnySubscripts/blob/master/src/main/kotlin/Store.kt Thanks
a
Synchronization in what regards? Mutex? Access to shared resource?
g
Access shared resource
would I need to synchronize{} within the actors on the shares resource
c
By having an “Intent” Actor dispatch to the sub-actors, you’re basically declaring that they should run in parallel, so you’d need some kind of additional synchronization to make them work properly if they’re accessing shared resources, which kinda defeats the purpose of using Actors. The Actor pattern by design has each Actor only processing local state, so that one is able to achieve lock-free parallel processing. Do you actually want them running in parallel, or is the intention that each Intent is processed sequentially in the order they were received? Are you using different Actors so that the handling of one Intent can schedule additional Intents for later processing? You may not need to use Actors at all (not to mention that the coroutine
actor
is marked obsolete)
g
ideally its each intent processed one after another, but the problem is these actors are not reentrant
Also the actor coroutine is marked obsolete it’s still useable and the Kotlin team say any changes when they introduce complex actors should be minimal anyways
c
Actors are themselves are basically just a Channel, with a default capacity of 0 (or RENDEZVOUS). This capacity is what opens the possibility of deadlocks. If, instead, you used a buffered channel, then you shouldn’t have a problem with the processing of one Intent dispatching more Intents into the same Channel. One other feature of Channels that is not exposed through
.actor()
is
BufferOverflow
, which by default is
SUSPEND
, but may be more useful to drop additional Intents if the buffer is full, to guarantee no deadlocks. For context, I’m writing an MVI library, ballast, which does exactly this same kind of thing. Given that
.actor
is effectively just an obsolete API over Channels but offers less functionality, you may be better-off just using a Channel directly. And with this use-case, it sounds like a single Channel is all you need, you don’t need a main one delegating to sub-channels (or sub-actors)
g
that sounds perfect for what am trying to do tbh, i’ll give it a look thanks 🙂
can I not just init the actor with buffered capacity for a quick test?
c
The
.actor()
API does allow you to configure the
capacity
so you could try it with
.actor(capacity = Channel.BUFFERED)
or
.actor(capacity = Channel.UNLIMITED)
, but it won’t allow you to configure the overflow behavior
g
but the overflow behaviour shouldnt be required for a quick test as long as I dont go past the limit which I think is around 64?
c
Yeah, that’s correct, the default buffered capacity should be 64
g
I think another critical thing I was doing was the
awaits()
c
Yeah, doing anything that suspends will “block” the whole processing queue until it completes, but if you need the Intents processed sequentially, you want this. It wont deadlock if you know those jobs will complete, but if they run infinitely long that is a risk. The solution is to run those long-running jobs in parallel, and you’d do yourself a favor here by having them post new Intents to make updates to the Store, rather than allowing those jobs to access the shared resources directly and try to synchronize with locks/mutexes. By doing it in this manner, you don’t need synchronization, and you still get isolated sequential updates to the Store to keep everything well-ordered. This is the purpose of the
sideJob
in Ballast
g
I am currently posting new Intents, but just going via the facade functions that create the
CompletableDeferred
The problem am having is sometimes I do need to return data from these actor messages. I was using the
CompletableDeferred
for that. This is all so much easier in swift 😂 add 1 keyword and away you go