I read that usage of Globalscope
is highly discouraged, here.
I have a simple use-case. For every kafka message (let's say a list of Ids) that I receive I have to split it and invoke a rest service simultaneously and wait for it to be done and proceed with other synchronous tasks. There is nothing else is in that application that requires coroutine. In this case, Can I just get away with it?
Note: This is not an android application. It's just a kafka stream processor running on server side. It's an ephemeral, stateless, containerized (Docker) application running in Kubernetes (Buzzword-compliant if you will)
You should scope your concurrency appropriately using structured concurrency. Your coroutines can leak if you don't do this. In your case, scoping them to the processing of a single message seems appropriate.
Here's an example:
If one of the calls to
restService.post(id)
fails with an exception, the example will immediately rethrow the exception, and all the jobs that hasn't completed yet will leak. They will continue to execute (potentially indefinitely), and if they fail, you won't know about it.To solve this, you need to scope your coroutines. Here's the same example without the leak:
In this case, if one of the calls to
restService.post(id)
fails, then all other non-completed coroutines inside the coroutine scope will get cancelled. When you leave the scope, you can be sure that you haven't leaked any coroutines.Also, because
coroutineScope
will wait until all child-coroutines are done, you can drop thejobs.joinAll()
call.Side note: A convention when writing a function that start some coroutines, is to let the caller decide the coroutine scope using the receiver parameter. Doing this with the
onMessage
function could look like this:By the docs using async or launch on the instance of
GlobalScope
is highly discouraged, application code usually should use application-definedCoroutineScope
.If we look at the definition of
GlobalScope
we will see that it is declared as object:An object represents a single static instance(Singleton). In Kotlin/JVM a static variable comes into existence when a class is loaded by the JVM and dies when the class is unloaded. When you first use of
GlobalScope
it will be loaded into the memory and stay there until one of the following happens:So it will consume some memory while your server application is running. Even if your server app is finished running but process is not destroyed, a launched coroutine may still be running and consume the memory.
Starting a new coroutine from the global scope using
GlobalScope.async
orGlobalScope.launch
will create a top-level "independent" coroutine.The mechanism providing the structure of the coroutines is called structured concurrency. Let's see what benefits structured concurrency has over global scopes:
When using
GlobalScope.async
there is no structure that binds several coroutines to a smaller scope. The coroutines started from the global scope are all independent; their lifetime is limited only by the lifetime of the whole application. It is possible to store a reference to the coroutine started from the global scope and wait for its completion or cancel it explicitly, but it won't happen automatically as it would with a structured one. If we want to cancel all coroutines in the scope, with structured concurrency, we only need to cancel the parent coroutine and this automatically propagates cancellation to all the child coroutines.If you don't need to scope a coroutine to a specific lifetime object and you want to launch a top-level independent coroutine which is operating on the whole application lifetime and is not cancelled prematurely and you don't want to use the benefits of the structured concurrency, then go ahead and use global scopes.
In your link it states:
My answer addresses this.
Generally speaking
GlobalScope
may bad idea, because it is not bound to any job. You should use it for the following:Which does not seem to be your usecase.
For more information there is a passage in the official docs at https://kotlinlang.org/docs/reference/coroutines/basics.html#structured-concurrency
So in essence it is discouraged, because it forces you to keep references and use
join
, which can be avoided with structured concurrency. (See code example above.) The article covers many of the subtleties.