I have something like: ```// an ongoing global sc...
# coroutines
z
I have something like:
Copy code
// an ongoing global scope that should never be cancelled
val scope = CoroutineScope(dispatchers.default)
However, a developer can easily call:
Copy code
scope.cancel()
Is there any standard way to prevent this? I’d like to throw an exception if a dev inadvertently calls
scope.cancel()
?
j
maybe do
GlobalScope + dispatchers.default
instead?
Or subclass
CoroutineScope
and return
dispatchers.default
from the context
(that's all GlobalScope is)
you're basically just trying to avoid having a
Job
z
note: I do want cancellation of the scope to crash the application. any error thrown in there should be terminal
j
I'm not sure you can get between
scope.cancel()
while also allowing regular exception propagation
z
while also allowing regular exception propagation
yeah, this is exactly what I would want ideally. Technically, no dev has accidentally cancelled a scope yet so maybe I’m overthinking this, but it would be nice to be defensive. Something I struggle with is how far do we go with structured concurrency. For example, it would be nice to build an app with a root scope from a
@Test
and cancel it at the end and have cancellation propagate correctly - but maybe that’s overthinking too.
s
Maybe writing a lint rule is an option 🙂 (and add explicit suppressions of that lint-rule where you want it called).
r
Maybe you can use a SupervisorScope and create child scopes within that scope (so that even if they get cancelled, the parent scope will not be cancelled). To crash the application in case an unrecoverable exception occurs in one of the child scopes, you can attach a
CoroutineExceptionHandler
to the child coroutines...
z
as for
SupervisorScope
- I want failures to affect children.
Copy code
Unlike coroutineScope, a failure of a child does not cause this scope to fail and does not affect its other children, so a custom policy for handling failures of its children can be implemented.
This is a global, ongoing scope that should never have a failure. I guess this doesn’t matter because if a dev cancels the scope incorrectly the whole app will stop working, lol
s
When you create a new CoroutineScope, you often do this to introduce a lifecycle. When a lifecycle is attached/introduced to an instance of some class through a CoroutineScope instance, you can hide it (make it private) to reduce the risk of external code cancelling it accidentally (ie the instance manages its own lifecycle). Unit-tests can then verify if the implementation of your class doesn't accidentally cancel its private CoroutineScope.
j
So can just do a completion handler on the job and just crash the app if it's a cancelation exception?
f
Copy code
class NonCancellableJob(job: Job = Job()): Job by job {
    override fun cancel(...) = error("Cannot cancel")
}
Copy code
fun CoroutineScope.asNonCancellable(): CoroutineScope = CoroutineScope(NonCancellableJob(coroutineContext.job))