arrow-kt / suspendapp

Reason about resource-safety in the same way you reason about Structured Concurrency with SuspendApp!
https://arrow-kt.github.io/suspendapp/
Apache License 2.0
74 stars 4 forks source link

Completing Backpressure Deferrable Deadlocks #109

Open rsteppac opened 9 months ago

rsteppac commented 9 months ago

Kotlin version: 1.9.21 Native Platform build: linuxX64 OS: Ubuntu 23.10

I tried the "Simple Example" from the README.MD, but the backpressure communication via the CompletableDeferred<Int> seems the get deadlocked and the simple example never terminates. Triggering the kill signal (SIGINT or SITGERM) gets correctly communicated to the arrow.continuations.unsafe.Unsafe#onShutdown hook, but the shutdown hook never returns from the call to BACKPRESSURE.complete(..) and the waiting signal handler never gets released. Providing a timeout to arrow.continuations.SuspendAppKt#SuspendApp has no effect.

nomisRev commented 9 months ago

Hey @rsteppac,

Thank you for this report! This might be related to #70. Strange that it works fine on MacOS given that it uses the same Kotlin/Native code.

Could you share the snippet you used to produce this?

rsteppac commented 9 months ago

@nomisRev , I have published a demonstrator here: https://github.com/rsteppac/kotlin-signalhandler-demo/tree/develop

I started out with suspendapp as a dependency, but for easier debugging (println everywhere) I copied the two source files required for a linux native build into my demonstrator repo.

When I run the executable and then try to interrupt it, then I get this output:

$ ./kotlin-signalhandler-demo-0.0.1-SNAPSHOT.kexe 
Unsafe.onShutdown: GlobalScope.launch
Unsafe.onShutdown: signal(SIGTERM, SignalHandler)
Unsafe.onShutdown: signal(SIGINT, SignalHandler)
Unsafe.onShutdown: return {}
App Started!  Waiting until asked to shutdown.
Unsafe.onShutdown: GlobalScope.launch: SIGNAL.await()
Ping
Ping
Ping
^CSignalHandler: signal code = 2
SignalHandler: BACKPRESSURE.await()
Unsafe.onShutdown: GlobalScope.launch: SIGNAL.await(): code = 2
Unsafe.onShutdown: GlobalScope.launch: runCatching { block() }
kotlinx.coroutines.JobCancellationException: LazyStandaloneCoroutine was cancelled; job=LazyStandaloneCoroutine{Cancelling}@6cb3c0e0
Cleaning up App... will take 2s...
Done cleaning up. Will release app to exit
Unsafe.onShutdown: GlobalScope.launch: runCatching { block() }: res = Success(kotlin.Unit)
Unsafe.onShutdown: GlobalScope.launch: BACKPRESSURE.complete
Killed

The second to last line is printed before the call to BACKPRESSURE.complete(res.fold({ code }, { -1 })). The last line of the output is the result of sending a SIGKILL to the stuck process.