Does `Mutex.tryLock()` always return true? Or does...
# coroutines
r
Does
Mutex.tryLock()
always return true? Or does it just not work like
ReentrantLock
?
e
Should not. It should return
false
if the mutex is taken and return.
r
Actually, I figured out I made a mistake, but it led to a confusing result.
Copy code
suspend inline fun <T> Mutex.test(wait: Boolean, action: () -> T): T? {
    return if (tryLock()) {
        try {
            action()
        } finally {
            unlock()
        }
    } else {
        if (wait) withLock(action = action) else null
    }
}

suspend fun Mutex.demo(message: String, delay: Long = 3000L, wait: Boolean = false) {
    if (
        test(wait) {
            println("start: $message")
            Thread.sleep(delay)
            println("end: $message")
        } == null
    ) {
        println("skip: $message")
    }
}

fun main(args: Array<String>) = runBlocking<Unit> {
    val lock = Mutex()
    launch { lock.demo("A", wait = false) }
    delay(50)
    launch { lock.demo("B", wait = false) }
    delay(50)
    launch { lock.demo("C", wait = false) }
}
Notice the
Thread.sleep()
in
demo()
. If I replace it with a
delay()
it works as expected, but with
Thread.sleep()
, they all wait. Is that expected?
z
That sounds right - If you call
Thread.sleep
it blocks your thread, so the
runBlocking
event loop can't process any more events, so all your coroutines will block.
r
Ah, indeed. That makes perfect sense. Maybe I need to take a break for a bit.
e
We’re actually planning an inspection to flag blocking calls like
Thread.sleep
inside coroutines. That would have helped to spot the problem
👍 5
r
Indeed, that would be nice
d
I wonder when a programmer really intends to
launch
a coroutine with the thread used by
runBlocking
. I have seen it come up as the answer to questions by confused developers multiple times.
u
@Dico what's wrong with launching something to the thread that sits in runBlocking?
d
I didn't claim that something is wrong with that. It's just an uncommon use case, which is easy to use by accident. If you have an outer
CoroutineScope
it will be shaded away completely by
runBlocking
which does not copy any of its context.
z
If you have an outer
CoroutineScope
, you should use something like
withContext
instead of calling
runBlocking
- because
runBlocking
is a blocking call, and making blocking calls from within a coroutine is discouraged. The issue is the blocking call, not the fact that all coroutines happen to be dispatched to the same thread.
d
Yeah, it's not idiomatic.
withContext
does something completely different though and is irrelevant. I'm not looking for advice for a project, I just brought up an easy little pitfall that every programmer might eventually run into. I wrote an issue in kotlinx.coroutines the other day if you want it explained better. Just to make sure it's considered.