https://kotlinlang.org logo
Title
s

Sanat Tripathi

05/17/2022, 8:45 PM
Hello folks, I have a question regarding
suspendCancellableCoroutine
and
Mutex
suspend fun awaitCallback(): T = suspendCancellableCoroutine { continuation ->

    val callback = object : Callback { // Implementation of some callback interface
        override fun onCompleted(value: T) {
            // Resume coroutine with a value provided by the callback
            continuation.resume(value)
        }
        override fun onApiError(cause: Throwable) {
            // Resume coroutine with an exception provided by the callback
            continuation.resumeWithException(cause)
        }
    }

    // Register callback with an API
    api.register(callback)
    // Remove callback on cancellation
    continuation.invokeOnCancellation { api.unregister(callback) }
    // At this point the coroutine is suspended by suspendCancellableCoroutine until callback fires
}

suspend fun execute() {
    val mutex = Mutex(false)
    
    mutex.lock()
 
 	try {
   		awaitCallback()    
    } catch(e: Throwable) {
        throw e
    } finally {
        mutex.unlock()
    }
 
}
In the above setup, the moment suspension happens at
suspendCancellableCoroutine
mutex in
execute
method gets unlocked part of the
finally
block
m

mkrussel

05/17/2022, 8:55 PM
Are you wondering why the unlock doesn't happen automatically and requires to manually unlock. If so you want to use https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/with-lock.html
s

Sanat Tripathi

05/17/2022, 8:59 PM
Yeah i like
withLock
API as well but this is so legacy code that im working with atm. And im actually trying to figure out why does the
unlock
get called right after the suspension happens inside
suspendCancellableCoroutine
I was hoping that it would wait for
continuation.resumeX
apis to get called and only then go to finally block
m

mkrussel

05/17/2022, 9:02 PM
I'm not positive. Continuations behave differently when they get compiled down to JVM bytecode. The call to
awaitCallback
does return on the JVM and then
execture
gets called again jumping to the correct continuation point. Not sure how finally blocks behave with that.
The source code for
withLock
looks like what you have there, except without the
catch
block.
Did you mean to be declaring a new
Mutex
in the
execute
function?
n

Nick Allen

05/18/2022, 12:32 AM
In the above setup, the moment suspension happens at
suspendCancellableCoroutine
mutex in
execute
method gets unlocked part of the
finally
block (edited)
There's only one coroutine in the snippet. Pretty sure all that code will run synchronously so it's not clear what you mean.
And im actually trying to figure out why does the
unlock
get called right after the suspension happens inside
suspendCancellableCoroutine
I was hoping that it would wait for
continuation.resumeX
apis to get called and only then go to finally block
That's how it works so again, not sure what you mean. Are you sure you aren't seeing executions for multiple concurrent coroutines or something. Also, if register calls
onCompleted
, directly then nothing will suspend. Maybe a https://play.kotlinlang.org/ sample with `println`s would help explain what you are seeing and you can post the output you're hoping for. Also,
suspendCancellableCoroutine
is meant for methods that will only invoke the callback once. If that's the case here then nvm, but
register
suggests that it's not and you may want to consider
callbackFlow { ... }.first()
to avoid errors in case you receive multiple callback invocations.
u

uli

05/21/2022, 6:20 PM
I agree with your expectations @Sanat Tripathi. How did you analyse that the behaviour is as you describe? I suggest to put logs in
onCompleted
,
onApiError
, the `catch`block (also logging the exception) and the
finally
block. If this proves your observation, please post the code here.
s

Sanat Tripathi

05/26/2022, 8:49 PM
Thank you guys, there was an exception being thrown and thats why the finally block was getting executed
👍 1