I'm trying to debug something that was working... ...
# coroutines
j
I'm trying to debug something that was working... I wonder if anyone can give me hints of things to try? I've only been using Kotlin coroutines a few days. I have a class (running in Spring Boot) that runs queue processing in the background, so on construction it has:
Copy code
GlobalScope.launch { runBlocking { setupRoutines() }}
there's a field
Copy code
val resolveChan = Channel<Envelope>(PARALLELISM * 2) // PARALLELISM = 200
and
setupRoutines()
does:
Copy code
jobs = (0..PARALLELISM).map {
  launch(CoroutineName("read-only-$it") + <http://Dispatchers.IO|Dispatchers.IO>) {
  <http://logger.info|logger.info>("Start RRO thread ${Thread.currentThread().name} from ${resolveChan}")
  try {
    for (envelope in resolveChan) {
    try {
      <http://logger.info|logger.info>("> RRO ${envelope.trace} ${Thread.currentThread().name}")
(try catch is mostly for debugging at this point) My class has a public method that enqueues envelopes:
Copy code
suspend fun ingestAsync(envelope: Envelope) {
    <http://logger.info|logger.info>("> Ingest async ${envelope.trace} to $resolveChan")
    resolveChan.send(envelope)
    <http://logger.info|logger.info>("< Ingest async ${envelope.trace} to $resolveChan")
}
My problem is that the envelopes never seem to get received in the for loop above, and there are no exceptions. Startup is fine:
Copy code
Start RRO thread DefaultDispatcher-worker-13 @read-only-199#202 from ArrayChannel@46794b2a{ReceiveQueued,queueSize=159}(buffer:capacity=400,size=0)
then it adds envelopes to the channel without errors, but they are never picked up.
Copy code
> Ingest async cdf83ca6-c756-49e0-a3ee-a4e309c4942a to ArrayChannel@46794b2a{ReceiveQueued,queueSize=94}(buffer:capacity=400,size=0)
 < Ingest async cdf83ca6-c756-49e0-a3ee-a4e309c4942a to ArrayChannel@46794b2a{ReceiveQueued,queueSize=93}(buffer:capacity=400,size=0)
That confirms that the object id for the channel is the same. I do notice that the
size
says at 0 the whole time, which is weird?
I think the answer was that I had too many concurrent coroutines starving the
<http://Dispatchers.IO|Dispatchers.IO>
threadpool.
r
Don't use `GlobalScope`; also,
runBlocking
is actually for starting coroutines from non-suspend functions, not running blocking code from inside coroutines (confusing name tbh). https://elizarov.medium.com/the-reason-to-avoid-globalscope-835337445abc
j
Thanks! I saw warnings, but never got as far as trying to find an alternative. Is there a simple answer to "how do I create a scope that runs forever in the background until I stop it?"
Everything I saw (as far as I understand it) expects for your outmost function to return, which doesn't apply to long-running processes that live until you shut down your whole process.
j
Is there a simple answer to "how do I create a scope that runs forever in the background until I stop it?"
@jwass scopes shouldn't be confused with jobs. A scope doesn't actually "run". A scope serves as a handle to control the lifetime of a group of coroutines. The point of structuring coroutines using scopes is that you can make sure you don't leak any coroutine by cancelling the scope (which cancels all running coroutine inside that scope or child scopes). Usually if you have a component with a lifecycle, you might want to tie all the coroutines it launches to its lifecycle. This is done by creating the scope in the component (e.g. at construction time using the
CoroutineScope()
factory function ) and making sure you cancel that scope at the end of the component's lifecycle (e.g. a
close
method or something similar). This way, when the component dies, all its coroutines die with it and you're not leaking anything.
GlobalScope
is a scope that is basically never cancelled until the process is terminated, which is why it's "dangerous" because some badly behaved coroutines could run forever there and never be cancelled, consuming resources. If you really mean "run this coroutine until the death of my process", you can use
GlobalScope
.
j
Thanks very much for those replies they were very useful. @Richard Gomez, re
runBlocking
, I think I'm using it correctly to fire off a background process from a non-suspended context (Spring running application hooks on startup). @Joffrey, that explanation was very useful, and makes much more sense than what I was originally trying to do. I've linked the scopes to the lifetime of the components and it's a lot cleaner.
j
@jwass your usage of
runBlocking
is actually incorrect because it's inside the launch, which itself starts a coroutine. You should either use
scope.launch {}
or
runBlocking
, but not the latter within the former
j
Yes, fixed that now.