Can somebody please explain GlobalScope.async to me?

I’m currently dealing with the following method:

// overrides an interface, can't make it suspending
override fun doSomeWork(): Deferred<T> {
    return GlobalScope.async {
        // actual work here
    }
} 

This seems to work. However, in some cases, when I call doSomeWork().await() in my code, the await() will suspend the calling coroutine forever (kind of like a dead-lock).

According to the kotlin docs:

Application code usually should use application-defined CoroutineScope, using async or launch on the instance of GlobalScope is highly discouraged.

However, the docs do not explain why it is discouraged. It seems like the most meaningful way to produce a Deferred<T> object inside a non-suspending function. Could somebody clarify this?

I set out to look for a different solution, and what I am doing now is this:

// overrides an interface, can't make it suspending
override fun doSomeWork(): Deferred<T> {
    var result: Deferred<T>? = null
    runBlocking {
        result = async {
            // actual work here
        }
    }
    return result!!
} 

In contrast to the solution above, this one actually works (no deadlocks). If you ask me, this second (rather convoluted) solution should be semantically equvialent to the first one, but apparently there is a difference (which I cannot see).

Could somebody help me unravel this mystery?

The coroutines are not safe from deadlocks. At least if you are using some blocking code somewhere. If you exhaust thread pool somewhere, you will have the lock. Scopes so not save you from deadlock, they provide a way to terminate dead coroutines.

In your case, you probably start additional coroutines inside async somewhere or event call your doSomeWork from a coroutine. In the last case. Using runBlocking could disentangle this lock, but it is really bad solution. Better to find the reason behind the lock.

Thanks but… that doesn’t really answer any of the questions above…

How would you debug this situation? I know how to do it with threads (basically fire up the profiler and do a thread dump, look at the stack trace), but with coroutines that method doesn’t help. I know for a fact that if I implemented the same functionality with threads, there would not be any chance of deadlocking in the code I’m working on (do a bunch of HTTP requests in parallel, collect the results, assemble a DTO, write it into a cache, return), it must have something to do with coroutines in particular.

I suggest you to take a look here

it contains some coroutines basics.

Hi folks,

so after digging very deep into the topic and learning a lot along the way, I realized that the coroutines are not to blame. I’m using KTOR, and need to call a REST microservice. For that, I used a HttpClient(Apache). It turns out that this very HTTP client library is known to cause indefinite waiting times. Well, this is precisely what hit me. As soon as I replaced it with HttpClient(CIO), everything went back to normal, regardless if I used GlobalScope.async or some otherScope.async.
I just leave this here in case that anybody else bumps into this. Be aware of the Apache HTTP client.

5 Likes