Is it ok to never join/cancel a `CoroutineScope` ?...
# coroutines
m
Is it ok to never join/cancel a
CoroutineScope
? If all the child coroutines eventually terminate, will everything be garbage collected or am I risking a memory leak somewhere?
c
Just to be clear you mean a scope created eg by factory, that’s a member on a class?
m
Something I create myself using
CoroutineScope()
👍 1
f
There is no memory leak technically. It's just conceptually correct to always close a scope, if you are adopting structured concurrency.
g
There is a risk of memory leak if you have never-ending operations on this scope, like someone observing StateFlow which never completes In general creation of CoroutineScope() is potentially not safe operation and should be used only when you create own lifecycle for this scope
m
There is a risk of memory leak if you have never-ending operations on this scope
👍 But if all my coroutines terminate then it's safe, right? Use case is a TCP connection keep alive. I'm fine with the connection stays alive ~30s in the background if it saves users a call to
.close()
g
Do you really need a separate scope for this? Wouldn't be better just use gloabalscope or some other higher level scope for this?
I just not sure why for described use case you need an own scope
I think the easiest way to reason about, is that scope is just a container for a job (other contexts too, but job is most important), and this job used to launch coroutines. As soon as all coroutines are finished and GCed, they do not prevent Job, and Scope from being collected too. scope will be collected as soon as nobody holds reference on it
👍 1
m
Makes sense, thanks! The idea was that
close()
could be used to force close the connection if you really need to reclaim resources faster but that the default would be to just let the connection time out.
g
You can close a particular job too. Or you have case when you send multiple requests in parallel and after this want to cancel them at some point (and nothing else, only requests)
m
Not 100% sure. Maybe tracking the job is enough indeed
Although there might be multiple requests each with their own coroutine
g
Yeah, I think there is nothing wrong with creating a some class-level scope for them which may be optionally cancelled on demand (so this class has its own scope with their own lifecycle) I only would recommend creating a job for this scope using a higher level scope job, if it available, so it has structured concurrency
👀 1
It still will be alive until all requests are finished or scope explicitly cancelled, so there is potential resource leak (some requests forever running and it will prevent gc coroutines, scope and enclosing class), but not necessary actual problem, because it can be exactly lifecycle which you want, just should remember about it
So In case of Android if this class with requests is created on level of activity (so not app-level), if you create it using activity lifecycle scope, it will not cause leak, but if you just do val scope = CoroutineScope() all requests could cause memory leak, they will not be automatically cancelled on activity destroy
m
It's a server so it's quite unlikely to be created from an Android activity but yea, I see what you mean
How common of a pattern is that? (asking for a parent Job?)
Compared to the good old implement
Closeable
?
g
Yeah, just as an example. On the server it can be a class created on every http request, so request has own lifecycle, but also it creates sub requests
👍 4
m
I'm afraid passing a
job
isn't that much of a pattern yet (but maybe it should)
g
I think passing scope is pretty common pattern
The only not usual thing, that you cannot create "child scope", you always should request job from scope to create own Job I wish it would be an API like fun CoroutineScope.chilsScope()
👀 1
👍 3
m
Will keep that in mind, thanks for the insights!
l
Using GlobalScope is risky. Your coroutines can get garbage collected if they arse waiting for a callback that is registered with a WeakReference under the hood (some Android APIs do that). Super hard to debug, if you ever notice the malfunction
m