Does the coroutines lib prioritize rescheduling fo...
# coroutines
w
Does the coroutines lib prioritize rescheduling for certain APIs? We’re having some strange delays in seemingly all places that use Channel whereas e.g. CompletableDeferred is rescheduled very quickly after completion.
d
We try to avoid extra context switches, if that's what you mean, but the channels were recently reworked. What you see may be a regression. Did this start happening recently, or did you always notice the strange delays?
w
We’re currently using 1.7.3, but I think we also had this issue with the 1.6.x series. Does the combination with
Channel.receiveAsFlow()
cause any additional overhead?
d
Not especially? It does have some overhead, but in most applications, it isn't expected to be noticeable. Without any measurements, it's hard to say if what you're seeing is expected.
w
OK I’ll try to get back once I’ve found the time to pin this down a little bit more. We’re in the 10s of milliseconds of delays for things that should take <1ms
This was just a pattern I’ve noticed in the code and in one place we switched from CompletableDeferred to Channel because we need multi-completion without losing results
d
I would expect sending to a channel to take 10x the time it takes to resume a
CompletableDeferred
. The latter is extremely simple: it's simply scheduling a task to a dispatcher, a straightforward single-producer, single-consumer task; with channels, there's much more bookkeeping exactly because there can be many producers and many consumers.
w
Does SharedFlow have the same overhead as Channel + receiveAsFlow()?
d
SharedFlow is an interface, not a specific implementation.
w
Right, I meant MutableSharedFlow
d
My intuition is that, for the single-consumer use case, it has more overhead, but tough to say without measuring it. Its purpose is mostly to provide a specific multi-consumer behavior.
w
Is there any better alternative to
CompletableDeferred
which can be awaited and completed multiple times?
d
Depends on what you mean by that. Could you provide an example?
w
On every button press I want to send a result to a different coroutine which is awaiting for that result, but if a new result comes in the coroutine should abort processing the previous one.
Or possibly it shouldn’t abort the previous result, but rather only remember the last unprocessed result.
w
Yes that’s what we’re doing basically
Plus launching a coroutine and aborting it
But it might be too slow. If, for example, we use a Channel for handling the text a user types into an input field our code seems to become slower than the user can type. But maybe that’s because there’s one more indirection involved. Still, I would’ve expected the scheduling to be on the order of <1ms
d
I'm probably misunderstanding this. Are your users typing faster than 10ms per character? That's better than the world record in typing speed!
w
Yeah that case might be different and have even higher delays due to an indirection where two channels are involved
…and collecting a MutableStateFlow etc.
The delays seem to accumulate very quickly to unreasonable amounts
d
If you find some specific example where a seemingly simple task takes an unreasonable amount of time, please do share it. Right now, this is a bit too fuzzy to make any conclusions.
w
I haven’t yet debugged those in detail. I just wanted to ask about any known issues because some of our changes hint towards our use of Channel
Once I’ve narrowed the cause down and if it’s really related to Channel I’ll create a ticket
d
Since 1.7.0, we have new
Channel
implementation that's supposed to perform much better, and since you're saying the problem is the same between 1.6.X and 1.7.X, maybe that's not it. Who knows!
w
Yes there could also be other reasons