package kotlinx.coroutines.sync import kotlinx.coroutines.testing.* import kotlinx.coroutines.* import kotlinx.coroutines.selects.* import kotlin.test.* class MutexTest : TestBase() { @Test fun testSimple() = runTest { val mutex = Mutex() expect(1) launch { expect(4) mutex.lock() // suspends expect(7) // now got lock mutex.unlock() expect(8) } expect(2) mutex.lock() // locked expect(3) yield() // yield to child expect(5) mutex.unlock() expect(6) yield() // now child has lock finish(9) } @Test fun tryLockTest() { val mutex = Mutex() assertFalse(mutex.isLocked) assertTrue(mutex.tryLock()) assertTrue(mutex.isLocked) assertFalse(mutex.tryLock()) assertTrue(mutex.isLocked) mutex.unlock() assertFalse(mutex.isLocked) assertTrue(mutex.tryLock()) assertTrue(mutex.isLocked) assertFalse(mutex.tryLock()) assertTrue(mutex.isLocked) mutex.unlock() assertFalse(mutex.isLocked) } @Test fun withLockTest() = runTest { val mutex = Mutex() assertFalse(mutex.isLocked) mutex.withLock { assertTrue(mutex.isLocked) } assertFalse(mutex.isLocked) } @Test fun testWithLockFailureUnlocksTheMutex() = runTest { val mutex = Mutex() assertFalse(mutex.isLocked) try { mutex.withLock { expect(1) assertTrue(mutex.isLocked) throw TestException() } } catch (e: TestException) { expect(2) } assertFalse(mutex.isLocked) finish(3) } @Test fun withLockOnEarlyReturnTest() = runTest { val mutex = Mutex() assertFalse(mutex.isLocked) suspend fun f() { mutex.withLock { assertTrue(mutex.isLocked) return@f } } f() assertFalse(mutex.isLocked) } @Test fun testUnconfinedStackOverflow() { val waiters = 10000 val mutex = Mutex(true) var done = 0 repeat(waiters) { GlobalScope.launch(Dispatchers.Unconfined) { // a lot of unconfined waiters mutex.withLock { done++ } } } mutex.unlock() // should not produce StackOverflowError assertEquals(waiters, done) } @Test fun holdLock() = runTest { val mutex = Mutex() val firstOwner = Any() val secondOwner = Any() // no lock assertFalse(mutex.holdsLock(firstOwner)) assertFalse(mutex.holdsLock(secondOwner)) // owner firstOwner mutex.lock(firstOwner) val secondLockJob = launch { mutex.lock(secondOwner) } assertTrue(mutex.holdsLock(firstOwner)) assertFalse(mutex.holdsLock(secondOwner)) // owner secondOwner mutex.unlock(firstOwner) secondLockJob.join() assertFalse(mutex.holdsLock(firstOwner)) assertTrue(mutex.holdsLock(secondOwner)) mutex.unlock(secondOwner) // no lock assertFalse(mutex.holdsLock(firstOwner)) assertFalse(mutex.holdsLock(secondOwner)) } @Test fun testUnlockWithNullOwner() { val owner = Any() val mutex = Mutex() assertTrue(mutex.tryLock(owner)) assertFailsWith { mutex.unlock(Any()) } mutex.unlock(null) assertFalse(mutex.holdsLock(owner)) assertFalse(mutex.isLocked) } @Test fun testUnlockWithoutOwnerWithLockedQueue() = runTest { val owner = Any() val owner2 = Any() val mutex = Mutex() assertTrue(mutex.tryLock(owner)) expect(1) launch { expect(2) mutex.lock(owner2) } yield() expect(3) assertFailsWith { mutex.unlock(owner2) } mutex.unlock() assertFalse(mutex.holdsLock(owner)) assertTrue(mutex.holdsLock(owner2)) finish(4) } @Test fun testIllegalStateInvariant() = runTest { val mutex = Mutex() val owner = Any() assertTrue(mutex.tryLock(owner)) assertFailsWith { mutex.tryLock(owner) } assertFailsWith { mutex.lock(owner) } assertFailsWith { select { mutex.onLock(owner) {} } } } @Test fun testWithLockJsMiscompilation() = runTest { // This is a reproducer for KT-58685 // On Kotlin/JS IR, the compiler miscompiles calls to 'unlock' in an inlined finally // This is visible on the withLock function // Until the compiler bug is fixed, this test case checks that we do not suffer from it val mutex = Mutex() assertFailsWith { try { mutex.withLock { null } ?: throw IndexOutOfBoundsException() // should throw… } catch (e: Exception) { throw e // …but instead fails here } } } }