How can I make sure the coroutine was in fact cancelled, and did not just return or was cancelled by other means?
There is an obvious race-condition between checking job state and invoking cancel()
. How can I know it was in fact cancelled?
val job = CoroutineScope(Dispatchers.Default).async { delay(1) }
if (!job.isCompleted)
job.cancel()
Maybe there is a better way, but one way is to cancel the job with a very specific exception and then check if it was cancelled with exactly the same exception. This is fairly easy if using Deferred
as in your example:
suspend fun main(): Unit = coroutineScope {
val job = GlobalScope.async {
delay(1.seconds)
println("Done")
}
launch {
delay(1.seconds)
println("Cancelled #1: " + job.cancelAndCheck())
}
launch {
delay(1.seconds)
println("Cancelled #2: " + job.cancelAndCheck())
}
}
suspend fun Deferred<*>.cancelAndCheck(): Boolean {
val e = CancellationException()
cancel(e)
join()
return getCompletionExceptionOrNull() === e
}
At least on my machine I sometimes see it was cancelled by #1, sometimes by #2 and sometimes by none (completed successfully). As this is a race condition, it may be hard or even impossible to reproduce on other computers.
Surprisingly, I don't see a similar API for the Job
. Obviously, we can't get a result from Job
, but I don't see why getCompletionExceptionOrNull()
couldn't be added at the Job
level, not Deferred
. The only way I found is by using the invokeOnCompletion
, but if feels a bit hacky:
suspend fun main(): Unit = coroutineScope {
val job = GlobalScope.launch {
delay(1.seconds)
println("Done")
}
launch {
delay(1.seconds)
println("Cancelled #1: " + job.cancelAndCheck())
}
launch {
delay(1.seconds)
println("Cancelled #2: " + job.cancelAndCheck())
}
}
suspend fun Job.cancelAndCheck(): Boolean {
val e = CancellationException()
cancel(e)
return suspendCancellableCoroutine { cont ->
val handle = invokeOnCompletion { cont.resume(it === e) }
cont.invokeOnCancellation { handle.dispose() }
}
}
Both solutions probably require additional testing for various cases like non-cancellable jobs, etc.