Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to use suspendCoroutine to turn java 7 future into kotlin suspending function

What is the best approach to wrap java 7 futures inside a kotlin suspend function? Is there a way to convert a method returning Java 7 futures into a suspending function?

The process is pretty straightforward for arbitrary callbacks or java 8 completablefutures, as illustrated for example here: * https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#suspending-functions

In these cases, there is a hook that is triggered when the future is done, so it can be used to resume the continuation as soon as the value of the future is ready (or an exception is triggered).

Java 7 futures however don't expose a method that is invoked when the computation is over.

Converting a Java 7 future to a Java 8 completable future is not an option in my codebase.

Of course, i can create a suspend function that calls future.get() but this would be blocking, which breaks the overall purpose of using coroutine suspension.

Another option would be to submit a runnable to a new thread executor, and inside the runnable call future.get() and invoke a callback. This wrapper will make the code looks like "non-blocking" from the consumer point of view, the coroutine can suspend, but under the hood we are still writing blocking code and we are creating a new thread just for the sake of blocking it

like image 321
alessandro candolini Avatar asked Jun 11 '18 08:06

alessandro candolini


2 Answers

Of course Roman is right that a Java Future does not let you provide a callback for when the work is done.

However, it does give you a way to check if the work is done, and if it is, then calling .get() won't block.

Luckily for us, we also have a cheap way to divert a thread to quickly do a poll check via coroutines.

Let's write that polling logic and also vend it as an extension method:

suspend fun <T> Future<T>.wait(): T {
    while(!isDone)
        delay(1) // or whatever you want your polling frequency to be
    return get()
}

Then to use:

fun someBlockingWork(): Future<String> { ... }

suspend fun useWork() {
    val result = someBlockingWork().wait()
    println("Result: $result")
}

So we have millisecond-response time to our Futures completing without using any extra threads.


And of course you'll want to add some upper bound to use as a timeout so you don't end up waiting forever. In that case, we can update the code just a little:

suspend fun <T> Future<T>.wait(timeoutMs: Int = 60000): T? {
    val start = System.currentTimeMillis()
    while (!isDone) {
        if (System.currentTimeMillis() - start > timeoutMs)
            return null
        delay(1)
    }
    return get()
}
like image 33
Matt Klein Avatar answered Sep 30 '22 11:09

Matt Klein


Java 7 future is blocking. It is not designed for asynchronous APIs and does not provide any way to install a callback that is invoked when the future is complete. It means that there is no direct way to use suspendCoroutine with it, because suspendCoroutine is designed for use with asynchronous callback-using APIs.

However, if your code is, in fact, running under JDK 8 or a newer version, there are high chances that the actual Future instance that you have in your code happens to implement CompletionStage interface at run-time. You can try to cast it to CompletionStage and use ready-to-use CompletionStage.await extension from kotlinx-coroutines-jdk8 module of kotlinx.coroutines library.

like image 80
Roman Elizarov Avatar answered Sep 30 '22 11:09

Roman Elizarov