:thread: Hi, I am trying to proof that `Mutex` run...
# coroutines
s
🧵 Hi, I am trying to proof that
Mutex
runs waiting tasks in a first come, first serve order as promised, but having some problems
The doc in the
Mutex
class mentions: "The mutex created is fair: lock is granted in first come, first served order."
I was trying to write a test to proof that order
Copy code
class MutexTest {
    @Test
    fun test2() = runTest {
        val subject = MyTestClass()

        val jobs1 = (1..10).map {
            launch(<http://Dispatchers.Default.io|Dispatchers.Default.io>) {
                subject.test("test1-$it")
            }
        }

        // Make sure all coroutines have finished
        jobs1.forEach { it.join() }

        // List are expected to be the same
        println(subject.hitList)
        println(subject.execList)
    }
}

private class MyTestClass {
    private val mutex = Mutex()
    val hitList = mutableListOf<String>()
    val execList = mutableListOf<String>()

    suspend fun test(param: String) {
        hitList.add(param)
        mutex.lock()
//        mutex.withLock {
        Thread.sleep(100)
        execList.add(param)
//         }
        mutex.unlock()
    }
}
one of the list records the order in which the
test()
method gets hit, the other one records the order in which the functionality is executed within the mutex lock. I would expect both lists to be equal at the end of the test, but unfortunately they are not
Example output
Copy code
[test1-1, test1-2, test1-3, test1-4, test1-5, test1-6, test1-7, test1-8, test1-9, test1-10]
[test1-1, test1-4, test1-2, test1-3, test1-10, test1-9, test1-7, test1-8, test1-6, test1-5]
Any idea if the issue is with the way i add items to the list or if
Mutex
actually runs the task in the wrong order?
k
Launch the jobs undispatched?
s
You mean all in the same coroutine? Then I would just call them one after each other and wait each time for the call to finish and the test would be pointless? I wouldn't even need a muted in that case
j
You're running coroutines in parallel, you're adding concurrently to a non thread-safe list, and you have no guarantee that the coroutines even attempt to acquire the mutex in the order you think they do (because after adding to the hit list there is no reason some other coroutine will not hit the mutex first). Ironically you would need synchronization or undispatched to guarantee that. Using undispatched is not the same as using one coroutine, it simply guarantees that the body of the coroutine is run immediately up to the first suspension point, so.. up to the mutex. Which is what you need.
☝🏻 1
☝️ 1
s
I see. Right, i was assuming what you are saying about adding the list items. Running it without using a dispatcher does indeed yield the expected result. I wonder though if the test is still as meaningful as if the coroutines would actually be started in parallel. Anyhow, it will have to do as proof for now. thank you color
For completeness, this is the final test case
Copy code
class MutexTest {

    @Test
    fun test() = runTest {
        val subject = MyTestClass()

        val jobs1 = (1..10).map {
            launch {
                subject.test1("test1-$it")
            }
            // I know i only map to the second one, but that's fine
            launch {
                subject.test2("test2-$it")
            }
        }

        // Make sure all coroutines have finished
        jobs1.forEach { it.join() }
    }
}

private class MyTestClass {
    private val mutex = Mutex()

    suspend fun test1(param: String) {
        println("Hitting test1: $param")
        mutex.withLock {
            println("Executing test1: $param")
            delay(100)
            Thread.sleep(100)
            println("Done test1: $param")
        }
    }

    suspend fun test2(param: String) {
        println("Hitting test2: $param")
        mutex.withLock {
            println("Executing test2: $param")
            delay(100)
            println("Done test2: $param")
        }
    }
}
test output shows that mutex synced blocks are executed in the same order as they were queued
Copy code
Hitting test1: test1-1
Executing test1: test1-1
Hitting test2: test2-1
Hitting test1: test1-2
Hitting test2: test2-2
Hitting test1: test1-3
Hitting test2: test2-3
Hitting test1: test1-4
Hitting test2: test2-4
Hitting test1: test1-5
Hitting test2: test2-5
Hitting test1: test1-6
Hitting test2: test2-6
Hitting test1: test1-7
Hitting test2: test2-7
Hitting test1: test1-8
Hitting test2: test2-8
Hitting test1: test1-9
Hitting test2: test2-9
Hitting test1: test1-10
Hitting test2: test2-10
Done test1: test1-1
Executing test2: test2-1
Done test2: test2-1
Executing test1: test1-2
Done test1: test1-2
Executing test2: test2-2
Done test2: test2-2
Executing test1: test1-3
Done test1: test1-3
Executing test2: test2-3
Done test2: test2-3
Executing test1: test1-4
Done test1: test1-4
Executing test2: test2-4
Done test2: test2-4
Executing test1: test1-5
Done test1: test1-5
Executing test2: test2-5
Done test2: test2-5
Executing test1: test1-6
Done test1: test1-6
Executing test2: test2-6
Done test2: test2-6
Executing test1: test1-7
Done test1: test1-7
Executing test2: test2-7
Done test2: test2-7
Executing test1: test1-8
Done test1: test1-8
Executing test2: test2-8
Done test2: test2-8
Executing test1: test1-9
Done test1: test1-9
Executing test2: test2-9
Done test2: test2-9
Executing test1: test1-10
Done test1: test1-10
Executing test2: test2-10
Done test2: test2-10
j
You can still launch using the dispatcher you want, but with
CoroutineStart.UNDISPATCHED
, so you guarantee the order in which they reach the suspension point.
But yeah it doesn't prove much anyway
sad panda 1