Reputation: 339
I have the following code structure:
@Throws(InterruptedException::class)
fun method() {
// do some blocking operations like Thread.sleep(...)
}
var job = launch {
method()
}
job.cancelAndJoin()
The method
is provided by the external library and I can't control its behaviour. It can take a lot of time for execution, so in some cases it should be canceled by timeout.
I can use the withTimeout
function provided by the kotlin coroutines library, but it can't cancel a code with blockings due to the coroutines design. It there some workaround to do it?
Upvotes: 7
Views: 3687
Reputation: 339
The main idea is to use the out of coroutines context thread pool with JVM threads that can be interrupted in the old style and subscribe to the cancellation event from the coroutine execution. When the event is caught by invokeOnCancellation
, we can interrupt the current thread.
The implementation:
val externalThreadPool = Executors.newCachedThreadPool()
suspend fun <T> withTimeoutOrInterrupt(timeMillis: Long, block: () -> T) {
withTimeout(timeMillis) {
suspendCancellableCoroutine<Unit> { cont ->
val future = externalThreadPool.submit {
try {
block()
cont.resumeWith(Result.success(Unit))
} catch (e: InterruptedException) {
cont.resumeWithException(CancellationException())
} catch (e: Throwable) {
cont.resumeWithException(e);
}
}
cont.invokeOnCancellation {
future.cancel(true)
}
}
}
}
It provides a similar behaviour like usual withTimeout
, but it additionally supports running a code with blockings.
Note: It should be called only when you know, that the inner code use blockings and can correctly process a thrown InterruptedException
. In most cases, the withTimeout
function is preferred.
UPDATE: Since the coroutines version 1.3.7, there has been a new function runInterruptible
, which provides the same behaviour. So this code can be simplified:
suspend fun <T> withTimeoutOrInterrupt(timeMillis: Long, block: () -> T) {
withTimeout(timeMillis) {
runInterruptible(Dispatchers.IO) {
block()
}
}
}
Upvotes: 10