`CoroutineScope.cancel` doesn't seem to work at le...
# coroutines
d
CoroutineScope.cancel
doesn't seem to work at least in the kotlin playground or I'm missing something. I expected the following to cancel on
i==6
but it doesn't. It throws the error after the loop finishes.
Copy code
import kotlinx.coroutines.*

@OptIn(DelicateCoroutinesApi::class)
suspend fun main() = coroutineScope {
    repeat(10) { i ->
        keepRunningUnlessFatal {
            if (i == 3) {
                println("throwing ISE")
                throw IllegalStateException("Illegal 3")
            }
            if (i == 6) {
                println("throwing OOM")
                throw OutOfMemoryError("OOM")
            }
            println("i ${i}")
        }
    }
 
}

suspend fun <T> CoroutineScope.keepRunningUnlessFatal(
    message: String = "Continuing execution after exception",
    block: suspend () -> T
): T? = try {
    block()
} catch (e: VirtualMachineError) {
    this.cancel("FATAL_ERROR_MSG", e)
    null
    // HERE - uncomment to see difference
    //throw e
} catch (e: InterruptedException) {
    this.cancel("FATAL_ERROR_MSG", e)
    null
} catch (e: LinkageError) {
    this.cancel("FATAL_ERROR_MSG", e)
    null
} catch (e: CancellationException) {
    this.cancel(e)
    null
} catch (e: Exception) {
    println(e)
    println(message)
    null
}
e
cancellation is cooperative. nothing in
repeat
or
keepRunningUnlessFatal
checks if the scope is alive
it would be observed if you called any function that actually suspends (like
suspendCoroutine
or things built on similar primitives, as most suspending functions are), or if you explicitly called
ensureActive()
d
yeah, i did ^^ in the toy example using both
ensureActive()
and
delay(i.milliseconds)
and it behaved as expected. good to know
Is that preferable to just
throw e
after the call to
cancel
?
t
if the scope is cancelled is there even anything left to throw to? we typically pepper this after all our runCatchings when in a suspend fun
Copy code
.onFailure { currentCoroutineContext().ensureActive() }
d
The throw seems to stop the coroutines non-cooperatively. That is, without waiting for the next suspend point.
t
Yeah peeling back ensureActive it just throws the CancellationException from the job so similar way to break out. I suspect the only real difference is down to the behavior of the CoroutineExceptionHandler in your Context since CancellationException is a documented special case?
IIRC the reason we spread ensureActive in our try/catch pattern is just to ensure we are not accidentally consuming CancellationException like we would an IOException and keeping a coroutine alive longer than we should
c