https://kotlinlang.org logo
k

Kieran Wallbanks

09/05/2022, 4:31 PM
So about
runBlocking
, I read that it shouldn't be used from a coroutine due to the potential for deadlocks (and we do seem to be encountering deadlocks here) but is there any way to "detect" if you're inside a coroutine somehow and run suspending methods in that coroutine instead of using
runBlocking
? For reference, we're working inside a lot of pre-existing Java code that obviously can't be suspending, so I'm not sure how else to bridge it other than
runBlocking
, which obviously isn't great and doesn't seem to be working for us.
m

Marc Knaup

09/05/2022, 4:34 PM
You use
runBlocking
to execute non-blocking coroutine code inside blocking non-coroutine code. To run blocking code from within a coroutine you’d use the opposite, for example:
Copy code
withContext(Dispatchers.Default) {
   // blocking stuff
}
k

Kieran Wallbanks

09/05/2022, 4:35 PM
Right, but we're not in a suspending method so we can't use
withContext
. For example
Copy code
override fun someJavaApiImpl() {
  runBlocking { someKotlinSuspendingMethod() }
}
a

Adam Powell

09/05/2022, 4:36 PM
when you're working with that api without coroutines, how would you set up a call to a method that invokes a callback when it's done?
think about it the same way; suspend functions have the same properties
j

Joffrey

09/05/2022, 4:39 PM
Adam already put it in a very nice way, actually. Let me expand from another point of view. Note that if you're inside a non-suspending function you cannot really know whether it was called from a coroutine or not. But anyway, this function blocks the caller thread until the end of its execution, it's kinda part of its API. If it needs to do something async, there is no way to wait for the result (or the completion of the work) without blocking the caller thread during this time.
runBlocking
is fine in places where you cannot make your function
suspend
(like implementing java interfaces) and a result is synchronously expected from you (or the work needs to be completed synchronously before returning) - you don't really have a choice in those cases. If a result is not expected from you (or the work does not need to be completed synchronously), and basically you can just "fire and forget", then it's another story. This means you can return from the non-suspending function before the work is done.
k

Kieran Wallbanks

09/05/2022, 4:42 PM
Right yeah that does make sense! So, from what I'm hearing
runBlocking
is fine to use and doesn't cause issues where it can deadlock if you use it inside places we can't use
suspend
but are done from suspending functions?
a

Adam Powell

09/05/2022, 4:43 PM
oh it can definitely cause issues. 🙂 If you're in a position where the method can be called from the call stack of an otherwise suspending function you probably shouldn't use
runBlocking
and look for a different solution instead
j

Joffrey

09/05/2022, 4:44 PM
and doesn't cause issues where it can deadlock
That's not what I meant, sorry if it was unclear. It can still cause issues if you're blocking the very thread that you're trying to dispatch on, for instance. What I meant is that you don't have a choice in that setup, and probably you should re-organize the calls differently if you really end up in deadlock situations.
a

Adam Powell

09/05/2022, 4:44 PM
where
runBlocking
shines is usually in cases that are entry points of some kind;
@Test
methods, request handlers for server frameworks that dedicate a thread per request, things like that
if you're in a heavily async or GUI environment,
runBlocking
probably shouldn't appear in your production code at all
j

Joffrey

09/05/2022, 4:44 PM
+1, or callbacks from libraries that use their own threading
a

Adam Powell

09/05/2022, 4:45 PM
even then, those libraries often expect that you won't wedge a thread for long periods, but it's highly situational
j

Joffrey

09/05/2022, 4:46 PM
yeah, and depending on the domain, you can usually wrap those in
suspendCancellableCoroutine
or
callbackFlow
k

Kieran Wallbanks

09/05/2022, 4:50 PM
It can still cause issues if you're blocking the very thread that you're trying to dispatch on, for instance.
Could you expand on what you mean by this perhaps? I guess, in one example, we were even running into situations where
runBlocking
inside, for example, a map's
computeIfAbsent
was causing issues and I'm just not sure if there's a real way around that. Like, is there not a way you can, say, "detect" if you're in a coroutine and just run it anyway? I don't even know if that would make sense :')
a

Adam Powell

09/05/2022, 4:55 PM
it wouldn't. 🙂 Suspending is a way to return a result from a function that means, "I'm not done yet, I'll give you a callback later." If you have a java function with a contract that says something like
public MyResult myFunction(MyParams params)
then when that returns, it has to actually return a
MyResult
, not an IOU for one later
there's no getting around that
k

Kieran Wallbanks

09/05/2022, 4:58 PM
Right yeah that does make sense - so, the "solution" is to just not use
runBlocking
where you don't know if you're in a coroutine or not, so, for the
computeIfAbsent
example, we should just rework the system to not even need a suspending function?
a

Adam Powell

09/05/2022, 5:01 PM
either not need a suspending function or be honest about the involvement of suspending work. For example, if you had a
Map<Key, Deferred<Value>>
instead of
Map<Key, Value>
then
computeIfAbsent
could be implemented using
myScope.async { computeWithSuspendStuff() }
j

Joffrey

09/05/2022, 5:05 PM
Could you expand on what you mean by this perhaps?
Well for starters, here is one example: https://discuss.kotlinlang.org/t/why-invoking-join-method-on-a-coroutine-having-mainscope-as-scope-stopping-it/25464
runBlocking
, by its very definition, blocks the thread that calls it while its body executes (the lambda that you pass to it). Inside the body, you can then call suspending functions normally. This is usually fine because internally,
runBlocking
uses the blocked thread to execute the body, creating an event loop out of it (but from outside of the body of
runBlocking
, the thread is still blocked). You might then try to switch dispatchers inside the body, like for instance
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
or something. In that case you're going outside the realm of that
runBlocking
body, and if ever you need to run something on the original thread again, and wait for its result, you will face a deadlock.
a

Adam Powell

09/05/2022, 5:16 PM
right.
runBlocking
makes it really easy to create reentrant call situations in code that is almost certainly not equipped to be called in a reentrant way
k

Kieran Wallbanks

09/05/2022, 5:36 PM
Ah okay, that makes a lot of sense. Thank you all for your time and patience!!
Out of curiosity, is there any real difference between
runBlocking
and doing something like
scope.future { }.get()
?
j

Joffrey

09/06/2022, 2:33 PM
Not with respect to this problem. It operates differently under the hood, but
.get()
blocks the current thread just like
runBlocking
, so this approach will have the same issues as
runBlocking
if it tries to re-enter that thread within the dispatched task.
8 Views