How to cancel kotlin coroutine with potentially "un-cancellable" method call inside it? - kotlin

I have this piece of code:
// this method is used to evaluate the input string, and it returns evaluation result in string format
fun process(input: String): String {
val timeoutMillis = 5000L
val page = browser.newPage()
try {
val result = runBlocking {
withTimeout(timeoutMillis) {
val result = page.evaluate(input).toString()
return#withTimeout result
}
}
return result
} catch (playwrightException: PlaywrightException) {
return "Could not parse template! '${playwrightException.localizedMessage}'"
} catch (timeoutException: TimeoutCancellationException) {
return "Could not parse template! (timeout)"
} finally {
page.close()
}
}
It should throw exception after 5 seconds if the method is taking too long to execute (example: input potentially contains infinite loop) but it doesent (becomes deadlock I assume) coz coroutines should be cooperative. But the method I am calling is from another library and I have no control over its computation (for sticking yield() or smth like it).
So the question is: is it even possible to timeout such coroutine? if yes, then how?
Should I use java thread insted and just kill it after some time?

But the method I am calling is from another library and I have no control over its computation (for sticking yield() or smth like it).
If that is the case, I see mainly 2 situations here:
the library is aware that this is a long-running operation and supports thread interrupts to cancel it. This is the case for Thread.sleep and some I/O operations.
the library function really does block the calling thread for the whole time of the operation, and wasn't designed to handle thread interrupts
Situation 1: the library function is interruptible
If you are lucky enough to be in situation 1, then simply wrap the library's call into a runInterruptible block, and the coroutines library will translate cancellation into thread interruptions:
fun main() {
runBlocking {
val elapsed = measureTimeMillis {
withTimeoutOrNull(100.milliseconds) {
runInterruptible {
interruptibleBlockingCall()
}
}
}
println("Done in ${elapsed}ms")
}
}
private fun interruptibleBlockingCall() {
Thread.sleep(3000)
}
Situation 2: the library function is NOT interruptible
In the more likely situation 2, you're kind of out of luck.
Should I use java thread insted and just kill it after some time?
There is no such thing as "killing a thread" in Java. See Why is Thread.stop deprecated?, or How do you kill a Thread in Java?.
In short, in that case you do not have a choice but to block some thread.
I do not know a solution to this problem that doesn't leak resources. Using an ExecutorService would not help if the task doesn't support thread interrupts - the threads will not die even with shutdownNow() (which uses interrupts).
Of course, the blocked thread doesn't have to be your thread. You can technically launch a separate coroutine on another thread (using another dispatcher if yours is single-threaded), to wrap the libary function call, and then join() the job inside a withTimeout to avoid waiting for it forever. That is however probably bad, because you're basically deferring the problem to whichever scope you use to launch the uncancellable task (this is actually why we can't use a simple withContext here).
If you use GlobalScope or another long-running scope, you effectively leak the hanging coroutine (without knowing for how long).
If you use a more local parent scope, you defer the problem to that scope. This is for instance the case if you use the scope of an enclosing runBlocking (like in your example), which makes this solution pointless:
fun main() {
val elapsed = measureTimeMillis {
doStuff()
}
println("Completely done in ${elapsed}ms")
}
private fun doStuff() {
runBlocking {
val nonCancellableJob = launch(Dispatchers.IO) {
uncancellableBlockingCall()
}
val elapsed = measureTimeMillis {
withTimeoutOrNull(100.milliseconds) {
nonCancellableJob.join()
}
}
println("Done waiting in ${elapsed}ms")
} // /!\ runBlocking will still wait here for the uncancellable child coroutine
}
// Thread.sleep is in fact interruptible but let's assume it's not for the sake of the example
private fun uncancellableBlockingCall() {
Thread.sleep(3000)
}
Outputs something like:
Done waiting in 122ms
Completely done in 3055ms
So the bottom line is either live with this long thing potentially hanging, or ask the developers of that library to handle interruption or make the task cancellable.

Related

Difference between GlobalScope and runBlocking when waiting for multiple async

I have a Kotlin Backend/server API using Ktor, and inside a certain endpoint's service logic I need to concurrently get details for a list of ids and then return it all to the client with the 200 response.
The way I wanted to do it is by using async{} and awaitAll()
However, I can't understand whether I should use runBlocking or GlobalScope.
What is really the difference here?
fun getDetails(): List<Detail> {
val fetched: MutableList<Details> = mutableListOf()
GlobalScope.launch { --> Option 1
runBlocking { ---> Option 2
Dispatchers.IO --> Option 3 (or any other dispatcher ..)
myIds.map { id ->
async {
val providerDetails = getDetails(id)
fetched += providerDetails
}
}.awaitAll()
}
return fetched
}
launch starts a coroutine that runs in parallel with your current code, so fetched would still be empty by the time your getDetails() function returns. The coroutine will continue running and mutating the List that you have passed out of the function while the code that retrieved the list already has the reference back and will be using it, so there's a pretty good chance of triggering a ConcurrentModificationException. Basically, this is not a viable solution at all.
runBlocking runs a coroutine while blocking the thread that called it. The coroutine will be completely finished before the return fetched line, so this will work if you are OK with blocking the calling thread.
Specifying a Dispatcher isn't an alternative to launch or runBlocking. It is an argument that you can add to either to determine the thread pool used for the coroutine and its children. Since you are doing IO and parallel work, you should probably be using runBlocking(Dispatchers.IO).
Your code can be simplified to avoid the extra, unnecessary mutable list:
fun getDetails(): List<Detail> = runBlocking(Dispatchers.IO) {
myIds.map { id ->
async {
getDetails(id)
}
}.awaitAll()
}
Note that this function will rethrow any exceptions thrown by getDetails().
If your project uses coroutines more generally, you probably have higher level coroutines running, in which case this should probably be a suspend function (non-blocking) instead:
suspend fun getDetails(): List<Detail> = withContext(Dispatchers.IO) {
myIds.map { id ->
async {
getDetails(id)
}
}.awaitAll()
}

Stop infinite function in kotlin using coroutines - difference between async and GlobalScope.async

I need to wrap some Java-callback function using timeout. Callback may be never called, so it should be interrupted with exception. Here was my first try:
fun main() = runBlocking {
withTimeout(500) {
async {
notCalledCallback()
}.await()
}
Unit
}
private suspend fun notCalledCallback() = suspendCoroutine<Boolean> { cont ->
startScanning(object : SomeCallback {
override fun done() {
cont.resume(true)
}
})
}
fun startScanning(callBack: SomeCallback) {
// callback may never be invoked
// callBack.done()
}
interface SomeCallback {
fun done()
}
I expected to have a TimeoutCancellationException after 500ms, but actually it never happens. However if I replace
async {
notCalledCallback()
}.await()
with
GlobalScope.async {
notCalledCallback()
}.await()
it starts to work. Why? What is the difference between async and GlobalScope.async in this case and why it works in latter case?
while (true) {
Thread.sleep(1)
}
This block of code does not comply with coroutine practices and doesn't offer the coroutine framework any opportunity to cancel it.
A correct implementation of infinityFunction() would be to simply call awaitCancellation. Alternately, you could replace Thread.sleep with delay.
Notably, using GlobalScope actually breaks the correct relationship between your coroutines (making the async block not a child of the calling coroutine), with the result that your main function doesn't wait for infinityFunction() to properly finish cancelling. While this appears to make your code work, it actually just conceals a worse bug.
The answer is actually very simple: suspendCoroutine() is not cancellable. You need to instead use a very similar function: suspendCancellableCoroutine().
Please be aware that ideally you should not only swap one function with another, but also properly cancel the asynchronous operation before resuming the coroutine. Otherwise you leak this background operation as it is entirely detached from your execution context. You can detect cancellations with cont.invokeOnCancellation(), as described in the documentation linked above.
If you use GlobalScope then you await() for the operation in your current execution context, but the operation itself runs in another context. In this case if you cancel, then you cancel waiting, but you don't cancel the operation and you don't care whether it completes or not.

Coroutine in Vertx never execute?

In Vert.x, suppose I have functions like this:
fun caller() {
runBlocking {
val job = GlobalScope.launch(vertx.dispatcher()) {
val r = suspendPart()
println(r) // never execute
}
println(1) // printed
job.join()
println(2) // never execute
}
}
suspend fun asyncPart(): Future<Int> {
val promise: Promise<Int> = Promise.promise()
delay(500)
promise.complete(0)
return promise.future()
}
suspend fun suspendPart(): Int {
return asyncPart().await()
}
r(which is 0) and 2 will never be printed, only 1 is printed. How should I fix it?
My intention is to wait for asyncPart completes (I have a AsyncResult inside actually).
Presumably your caller() method is called by vert.x and this means you're breaking one of the pivotal rules of vert.x:
Don’t block me!
Vert.x is mostly based on very fast single-threaded work, what this means is that when you block the thread in caller, it is unable to execute the coroutine scheduled with launch leading to a deadlock.
The proper way to solve this is to remove your blocking code through the integration vert.x provides for kotlin coroutines.
Alternatively using a different dispatcher for launch would also work since the other thread would unblock the vert.x dispatcher. But this would not solve the primary issue of blocking calls in the vert.x dispatcher.

Kotlin coroutines - how to run in background and use result in the caller thread?

The main idea is to have non-suspend function runInBackgroundAndUseInCallerThread(callback: (SomeModel) -> Unit) which run some work asynchronously in background (another thread) and after work is done - run callback in the caller thread (thread that launched runInBackgroundAndUseInCallerThread).
Below I wrote an example code, but I'm not sure how correct it is and whether it is possible at all. With the println("1/2/3/...") I marked the desired call order.
getDispatcherFromCurrentThread - if is possible to implement this function, then solution can be used, but I don't know how to implement it and is it right to do it like that at all.
Therefore, please do not consider it as the only solution.
import kotlinx.coroutines.*
import kotlin.concurrent.thread
fun main() {
println("1")
runInBackgroundAndUseInCallerThread {
println("4")
println("Hello ${it.someField} from ${Thread.currentThread().name}") // should be "Hello TestField from main"
}
println("2")
thread(name = "Second thread") {
runInBackgroundAndUseInCallerThread {
println("5")
println("Hello ${it.someField} from ${Thread.currentThread().name}") // should be "Hello TestField from Second thread"
}
}
println("3")
Thread.sleep(3000)
println("6")
}
fun runInBackgroundAndUseInCallerThread(callback: (SomeModel) -> Unit) {
val dispatcherFromCallerThread: CoroutineDispatcher = getDispatcherFromCurrentThread()
CoroutineScope(Dispatchers.IO).launch {
val result: SomeModel = getModelResult()
launch(dispatcherFromCallerThread) { callback(result) }
}
}
data class SomeModel(val someField: String)
suspend fun getModelResult(): SomeModel {
delay(1000)
return SomeModel("TestField")
}
fun getDispatcherFromCurrentThread(): CoroutineDispatcher {
// TODO: Create dispatcher from current thread... How to do that?
}
Unless the thread is designed to work as a dispatcher there isn't a universal way to make it do so.
The only way which comes to mind is the fact that runBlocking is re-entrant and will create an event-loop in the existing thread, however it will block all non-coroutine code from executing on that thread until it completes.
This ends up looking like:
fun runInBackgroundAndUseInCallerThread(callback: (SomeModel) -> Unit) {
callback(runBlocking(Dispatchers.IO) {
getModelResult()
})
}
dispatcher really is a coroutineContext and it is meaningful when used inside a scope
thus if you want pass dispatcher of parent scope to child scope you can do it.
GlobalScope.launch {
val dispatcher = this.coroutineContext
CoroutineScope(dispatcher).launch {
}
}
therefor getDispatcherFromCurrentThread should be like this.
fun getDispatcherFromCurrentThread(scope: CoroutineScope): CoroutineContext {
return scope.coroutineContext
}
and
GlobalScope.launch {
val dispatcher = getDispatcherFromCurrentThread(this)
CoroutineScope(dispatcher).launch {
}
}
which run some work asynchronously in background (another thread) and after work is done - run callback in the caller thread
First try to answer this question: what is the calling thread supposed to do while the background work is in progress?
Clearly it can't go on to the next line of your code, which is supposed to run after finishing the background work.
You also don't want it to block and wait.
What code should it run, then?
And the only reasonable answer is as follows: the calling thread should, at its topmost level of execution (entry-point function), run an infinite event loop. The code in your question should be inside an event handler submitted to the event loop. At the point you want to wait for the background work, the handler must return so the thread can go on handling other events, and you must have another handler ready to submit when the background work is done. This second handler, corresponding to your callback, is called the continuation and Kotlin provides it automatically. You don't in fact need your own callback.
However, now the most sensitive issue arises: how will you submit the continuation to the event loop? This is not something you can abstract over, you must use some API specific to the event loop in question.
And this is why Kotlin has the notion of a Dispatcher. It captures the case-specific concern of dispatching continuations to the desired thread. You seem to want to solve it without the need to write a dispatcher dedicated to each specific event loop, and unfortunately this is impossible.

Project Reactor - subscribe on parallel scheduler doesn't work

I'm looking at examples and reading documentation and I've found some problems while trying to subscribe on Flux in a parallel manner.
I have a 3 functions, as below.
private val log = LoggerFactory.getLogger("main")
private val sequence = Flux.just(1, 2)
fun a() {
sequence.subscribeOn(Schedulers.parallel()).subscribe { log.info("*** {}", it) }
sequence.subscribe { log.info(">>> {}", it) }
}
fun b() {
sequence.subscribe { log.info(">>> {}", it) }
}
fun c() {
sequence.subscribeOn(Schedulers.parallel()).subscribe { log.info("*** {}", it) }
}
Now, when I run each method separately I have a proper output from functions a() and b(), but output from c() is empty. Is that to be expected, is it by design? If so, why is that happening?
Flux.just(...) captures value(s) and thus is optimized to execute immediately in the subscribing Thread.
When you use subscribeOn, you change that subscribing Thread from main to something else, making the just truly asynchronous.
In a(), without a subscribeOn that second just would block the main thread just enough that the test doesn't finish before the asynchronous alternative completes.
In c(), there is no such blocking of the main thread. As a consequence, the test terminates before the asynchronous just has had time to emit anything, and that is why you see no output.
To make that more visible, add a Thread.sleep(10) and you'll see some output.