Stephen Edwards
02/24/2022, 3:42 PMrunBlocking
claims it blocks the thread from which it is called (interuptibly). But I have observed behaviour where other coroutines waiting for dispatch on that thread (e.g. on the main thread) will be dispatched by the event loop because runBlocking
falls back to using the calling thread's eventLoop
even when a specific other dispatcher (which may be busy) is specified for that which to run the coroutine launched by runBlocking
on. Why is this and is this a bug? If not what is the explanation for this choice of behaviour? Code reproduction example in thread:Stephen Edwards
02/24/2022, 3:43 PMrunBlocking
has a note about not using it from within a coroutine - but I'm not sure it's possible to know that as you could be running on any coroutine but not within a suspend function and you wouldn't know your execution context.Stephen Edwards
02/24/2022, 3:44 PMExecutor
wrapped coroutine dispatcher is just that this is where I first observed the behaviour as the testThreadDispatcher
in this case can't get the work dispatched immediately. There may be a simpler way to reproduce.Stephen Edwards
02/24/2022, 3:45 PMA
, D
, B
,C
then hangs. I would have thought the coroutine for B
and C
would never have gotten dispatched but runBlocking
picks up the calling threads eventLoop
to processNextEvent
while waiting?? It should be blocked though?ephemient
02/24/2022, 3:50 PMStephen Edwards
02/24/2022, 3:51 PMSam
02/24/2022, 3:51 PMrunBlocking
is desirable 😄. Solution: don't use it. Ever.ephemient
02/24/2022, 3:52 PMStephen Edwards
02/24/2022, 3:52 PMStephen Edwards
02/24/2022, 3:53 PMSam
02/24/2022, 3:53 PMStephen Edwards
02/24/2022, 3:56 PMrunBlocking
- you have no way of knowing this, e.g. as a library developerStephen Edwards
02/24/2022, 3:56 PMCasey Brooks
02/24/2022, 3:56 PMrunBlocking
to create top-level coroutines. I can't think of any valid reason why one would ever use runBlocking
inside of an existing coroutine. I'd think withContext(newFixedThreadPoolContext(1, name))
would be a better way to go about making an existing coroutine single-threadedStephen Edwards
02/24/2022, 3:57 PMStephen Edwards
02/24/2022, 3:57 PMephemient
02/24/2022, 3:57 PMephemient
02/24/2022, 3:58 PMStephen Edwards
02/24/2022, 3:58 PMSam
02/24/2022, 3:58 PMI'm not sure it's possible to know that as you could be running on any coroutine but not within a suspend function and you wouldn't know your execution context.☝️ I think this point is crucial, and bit me quite a few times when converting an existing application to use coroutines
Stephen Edwards
02/24/2022, 3:59 PMCasey Brooks
02/24/2022, 4:00 PMrunBlocking
, because it has no way to tie into a parent coroutine's context/jobephemient
02/24/2022, 4:00 PMephemient
02/24/2022, 5:33 PMStephen Edwards
02/24/2022, 6:38 PMZach Klippenstein (he/him) [MOD]
02/24/2022, 7:08 PMStephen Edwards
02/24/2022, 7:21 PMrunBlocking
? I am being explicit about the dispatcher thereephemient
02/24/2022, 7:22 PMZach Klippenstein (he/him) [MOD]
02/24/2022, 7:29 PMrunBlocking
start its coroutine undispatched? if it does, then if your runBlocking lambda never suspends it would never have a chance to hop threads. Curious what would happen if you did
runBlocking {
withContext(dispatcher) { … }
}
instead, or even
someScopeWithDispatcher.launch { … }
.let {
runBlocking {
it.join()
}
}
Stephen Edwards
02/25/2022, 4:00 PMStephen Edwards
02/25/2022, 4:00 PMStephen Edwards
02/25/2022, 4:01 PMEventLoop
of the calling context, its that we can't not get it. In my understanding we shouldn't be dispatching on the EventLoop
while blocked.Stephen Edwards
02/25/2022, 4:08 PMrunBlocking
pull out the EventLoop
of the ThreadLocal
?
if (contextInterceptor == null) {
// create or use private event loop if no dispatcher is specified
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
} else {
// See if context's interceptor is an event loop that we shall use (to support TestContext)
// or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
}
Since it processes the next event on that EventLoop
while blocking if it is available we by definition won't be blocking the ThreadLocal
thread, which it claims it is.
while (true) {
@Suppress("DEPRECATION")
if (Thread.interrupted()) throw InterruptedException().also { cancelCoroutine(it) }
val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
// note: process next even may loose unpark flag, so check if completed before parking
if (isCompleted) break
parkNanos(this, parkNanos)
}
My understanding would have been that we would only processNextEvent
on the EventLoop
if was passed in as the contextInterceptor
and shouldBeProcessedFromContext()
. That way we don't block that Dispatcher/EventLoop but we do block the ThreadLocal
thread, which is what the context specifies we should do.Stephen Edwards
02/28/2022, 9:19 PMZach Klippenstein (he/him) [MOD]
03/01/2022, 4:11 PMStephen Edwards
03/01/2022, 6:20 PMrunBlocking
? Given that the documentation states that runBlocking
will block the calling thread, this seems contradictory.Zach Klippenstein (he/him) [MOD]
03/02/2022, 5:02 PMephemient
03/02/2022, 6:29 PMStephen Edwards
03/02/2022, 7:58 PMdoesn't promise that no other coroutines are runWell, it promises that the Thread is blocked, which I guess is vague in this case - but I would have interpreted it as not running other coroutines dispatched on that thread until those dispatched within
runBlocking
are complete.Stephen Edwards
03/02/2022, 8:00 PMSo you're just curious? Have you dug through the coroutines source code? It's usually pretty well documented and sometimes explains intent for stuff like this.As far as the
runBlocking
builder and the BlockingCoroutine
and the EventLoop
. I understand how the EventLoop
is needed to prevent deadlock and keep things moving.
What I want though is a mechanism to provide my own EventLoop
(can't because internal) to runBlocking
such that it will dispatch only coroutines queued for dispatch within the runBlocking
lambda. That's what my interpretation of it thinks that runBlocking
should be doing anyway.Zach Klippenstein (he/him) [MOD]
03/03/2022, 2:22 AMephemient
03/03/2022, 3:37 AMThis function should not be used from a coroutineyou're outside of `runBlocking`'s use case to begin with
Stephen Edwards
03/03/2022, 3:38 PMStephen Edwards
03/03/2022, 3:38 PMephemient
03/03/2022, 3:39 PMStephen Edwards
03/03/2022, 3:40 PMyou can't dynamically know, but you should structure your code so that you statically knowThat's valid I guess if we decide never to use this in any library or shared ("distant") code. seems like a strong restriction.
ephemient
03/03/2022, 3:43 PMStephen Edwards
03/03/2022, 3:44 PMephemient
03/03/2022, 3:44 PMephemient
03/03/2022, 3:45 PMStephen Edwards
03/03/2022, 3:53 PMZach Klippenstein (he/him) [MOD]
03/03/2022, 5:08 PMStephen Edwards
03/03/2022, 6:19 PMZach Klippenstein (he/him) [MOD]
03/04/2022, 8:11 PMStephen Edwards
03/04/2022, 8:32 PMStephen Edwards
03/04/2022, 8:35 PM