I'd like to wrap a blocking call so that it can be...
# coroutines
h
I'd like to wrap a blocking call so that it can be used in a cancellable coroutine, i.e. add preemption. Clearly this is going against the grain, as the whole point of coroutines and their cancellation is cooperation, but still, I'd like to wrap this blocking interface once and for all so that it can be used without fear within coroutines. I have looked at https://alexsaveau.dev/blog/kotlin/android/advanced-kotlin-coroutines-tips-and-tricks and https://discuss.kotlinlang.org/t/coroutines-and-cancelling-blocking-code/12165, but I can't get my code to work. Here's some code exhibiting the issue (perhaps needless to say, this is simplified example code and not my production code, but the issue's the same) :
Copy code
// You are not allowed to change this class.
class Foo : AutoCloseable {
    private val outstream = PipedOutputStream()
    private val instream = PipedInputStream(outstream)

    override fun close() {
        instream.close()
    }

    fun blockingCall() {
        instream.read() // blocks the thread until e.g. the stream is closed
    }
}

/**
 * Wraps the AutoCloseable usage so that the AutoCloseable is closed when the coroutine is cancelled.
 */
suspend fun <T: AutoCloseable> closeOnCancel(block: (T) -> Unit) {
    // How do I implement this function?
    // I have tried using suspendCancellableCoroutine, but I think I misunderstood that function.
}

fun cancelBlockingCall() {
    val foo = Foo()
    runBlocking(<http://Dispatchers.IO|Dispatchers.IO>) {
        withTimeout(100) {
            foo.closeOnCancel { it.blockingCall() }
        }
        println("Yay, arriving here means problem solved")
    }
}
Could I get some help or pointers to documentation regarding how to implement
closeOnCancel
in general?
b
These blocking calls usually support cancellation via interruption, and corouitines has runInterruptible method, so you can wrap your call into it
👍 1
h
i'll try
runInterruptible
, to see if it works. however, if possible, i'd like to find a way to trigger that
close
call too, because such a technique feels like a cleaner way to wrap the call, as it would use the blocking API the way it was meant to.
Yes, this snippet
Copy code
fun cancelBlockingCall() {
    val foo = Foo()
    runBlocking {
        try {
            withTimeout(100) {
                runInterruptible(<http://Dispatchers.IO|Dispatchers.IO>) { foo.blockingCall() }
            }
        } catch (e: InterruptedIOException) {
            // Expected, no-op.
        }
        println("Yay, arriving here means problem solved")
    }
}
works, at least in my example code. (Let's hope it works in my real scenario too. 🤞) Still, I'd like to drill down to see if I can set up a solution where I call the
close()
method, if nothing else so as to gain some coroutine knowledge.
It turned out I couldn't use it in my real scenario, because there I'm stuck with coroutines library v. 1.3.1, but
runInterruptible
is only available from v. 1.3.6 onwards. 😞 So I'm still looking for solutions. (No, upgrading the coroutines library is not an option.)
j
If it helps, we've bridged a blocking jooq query into a cancellable Flow that cancels the underlying jdbc query when the coroutine is cancelled using a second monitor coroutine: https://github.com/trib3/leakycauldron/blob/main/db/src/main/kotlin/com/trib3/db/jooqext/ResultQueryFlow.kt
👀 1
h
@Joe interesting idea, having a "sibling" non-blocking coroutine that cancels the blocking call of the first coroutine. I like it, not the least because it's conceptually fairly clean and simple: I'll try it out and see if I can get it to work.