Victor Khovanskiy
Victor Khovanskiy

Reputation: 339

How to cancel the blocking code in the coroutines

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

Answers (1)

Victor Khovanskiy
Victor Khovanskiy

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

Related Questions