Description
Hi - Would it be possible to offer an overload / alternative version of runBlocking
that is 'uninterruptible' for JVM, which means that upon the caller thread of runBlocking
being interrupted, that runBlocking
will not throw an InterruptedException
(relevant source code).
Our reason for this request is that we migrated the internals of our libraries to Coroutines, and to keep things simple we have a 'pool of resources' that is suspending (acquiring the resources is suspending) but our APIs have both blocking and suspending versions, the blocking one being kept for compatibility reasons. We opted to use runBlocking
to bridge between existing blocking functionality with Coroutines internals but it has introduced the behavior that if the caller thread is interrupted then a InterruptedException
is thrown where as before, interruption was ignored by our library. I can acknowledge that the behavior change is 'better' in the sense that once the thread is interrupted the Coroutine is cancelled and work is 'stopped' more quickly, reducing cycles wasted, but the at the same time I have to sympathise with our users who for a long time had never expected our library to throw this exception but now it does.
It would greatly help us if there was a version of runBlocking
that took an extra parameter named interruptible
, by default being true
to keep current behavior, but that if set to false
it would simply reset the interrupt flag and not cancel the coroutine, therefore making it uninterruptible. Example:
runBlocking(interruptible = false) { ... }
.
I am also open to suggestions with how we could deal with this from our library, considerations so far have been:
- Catching the
InterruptedException
in a loop and simply retrying again:
while (true) {
try {
return runBlocking {
// ...
}
} catch (ex: InterruptedException) {
// ignored
}
}
This is not great because the Coroutine could have executed enough such that repeating the operation would cause a failure. A more real world example is an insertion of a row in a database table with a unique key, repeating the operation would be a unique key constraint violation.
- Creating our own
runBlockingUninterruptible
:
internal fun <T> runBlockingUninterruptible(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
val currentThread = Thread.currentThread()
var result: Result<T> = Result.failure(IllegalStateException("Coroutine has not completed yet."))
val coroutine = GlobalScope.launch(context) {
result = runCatching { block() }
}
coroutine.invokeOnCompletion {
LockSupport.unpark(currentThread)
}
var wasInterrupted = false
while (!coroutine.isCompleted) {
LockSupport.parkNanos(Long.MAX_VALUE)
wasInterrupted = Thread.interrupted()
}
if (wasInterrupted) {
Thread.interrupted()
}
return result.getOrThrow()
}
This is also not great because there is a thread hop that didn't use to exist, and there is a lot of complexities with the thread pool utilized for the coroutine (might be limited). Even though runBlocking
's event loop is an implementation detail, we would like to take advantage of it and enable re-using it.
- Catch the exception and return some default value:
This is not possible since from a library standpoint we have no knowledge of what would be a 'good' default value. Our bridge also uses a type argument for its return type which further prevents determining the correct value to return. Ultimately it can also be misleading as further processing would be using an incorrect value.
Thanks!