Hi - we've got an app written using JVM Threads fo...
# coroutines
r
Hi - we've got an app written using JVM Threads for parallelism / concurrency. We need to move one of the modules in it to coroutines. This module currently supports cancellation using
Thread.interrupt()
by checking
Thread.currentThread().isInterrupted
. Migrating that to
currentCoroutineContext().isActive
seemed fairly easy, but higher up the tree, and outside coroutine context, I now need to launch a coroutine and cancel it when the launching thread is interrupted, to maintain the contract. I've tried this:
Copy code
fun run() = runBlocking {
    val deferred = async(start = LAZY) {
      login.login()
    }
    try {
      deferred.await()
    } catch (_: InterruptedException) {
      deferred.cancel()
      deferred.await()
    }
  }
but my tests are failing in a way that shows that the contract has changed - indeed, it looks like the InterruptedException is not caught by that catch block at all because the lambda passed to
runBlocking
is being run in a different thread to
fun run()
. But I can't get a reference to
deferred
outside
runBlocking
! How are coroutines and threads meant to interop for cancellation?
j
Doesn't
runBlocking
propagate interruption as cancellation by default? Like doesn't it work if you just run your code in
runBlocking
directly?
r
No - I get an InterruptedException thrown in the calling thread
(I wonder if things are further complicated by testing using kotest? So I'm actually going kotest coroutine launches thread which does runBlocking, kotest coroutine interrupts thread)
My test looks like this:
Copy code
"failure event is triggered when cancelled" {
    // when
    var exitStatus: ExitStatus? = null
    val thread = thread { exitStatus = command.run() }

    // and
    thread.interrupt()

    // and
    thread.join()

    // then
    exitStatus shouldBe CANCELLED
  }
If I make
command.run()
just:
Copy code
fun run() = runBlocking {
  login.login().exitStatus
}
I get
java.lang.AssertionError: Expected CANCELLED but actual was null
as the test failure, and also see this exception printed to stderr:
Copy code
Exception in thread "Thread-0" java.lang.InterruptedException
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:98)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:70)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at wiremock.cli.core.authentication.LoginCommand.run(LoginCommand.kt:24)
	at wiremock.cli.core.authentication.LoginCommandSpec$1$11.invokeSuspend$lambda$0(LoginCommandSpec.kt:405)
	at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
r
Isn't it
runInterruptible
No sorry that's binding the other way
e
can you just use
async { login.login() }.asCompletableFuture().get().exitStatus
or something like that? https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.future/as-completable-future.html is supposed to propagate cancellation properly
or actually https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.future/future.html is probably a better fit,
Copy code
fun run() = future { login.login() }.get()
r
Thanks for the pointers, tried several versions, still no go. Bit pressed for time to explain what I've tried and what happened, sorry, will try and update later.
l
Your probably want to handle exceptions on an intermediate local
coroutineScope { … }