jwass
10/21/2021, 12:21 PMGlobalScope.launch { runBlocking { setupRoutines() }}
there's a field
val resolveChan = Channel<Envelope>(PARALLELISM * 2) // PARALLELISM = 200
and setupRoutines()
does:
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:
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:
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.
> 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?jwass
10/21/2021, 1:18 PM<http://Dispatchers.IO|Dispatchers.IO>
threadpool.Richard Gomez
10/21/2021, 1:19 PMrunBlocking
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-835337445abcjwass
10/21/2021, 1:26 PMjwass
10/21/2021, 1:27 PMJoffrey
10/21/2021, 3:47 PMIs 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
.jwass
10/21/2021, 7:24 PMrunBlocking
, 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.Joffrey
10/21/2021, 7:25 PMrunBlocking
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 formerjwass
10/21/2021, 7:28 PM