Skip to content

Commit a5dfc23

Browse files
committed
Fix cancellation support for Mutex when lock becomes available between tryLock and suspending.
1 parent 37b95a9 commit a5dfc23

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

kotlinx-coroutines-core/common/src/sync/Mutex.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,17 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2<Any?, Mutex> {
201201
// try lock
202202
val update = if (owner == null) EMPTY_LOCKED else Empty(owner)
203203
if (_state.compareAndSet(state, update)) { // locked
204-
cont.resume(Unit)
204+
val token = cont.tryResume(Unit, idempotent = null) {
205+
// if this continuation gets cancelled during dispatch to the caller, then release
206+
// the lock
207+
unlock(owner)
208+
}
209+
if (token != null) {
210+
cont.completeResume(token)
211+
} else {
212+
// failure to get token implies already cancelled
213+
unlock(owner)
214+
}
205215
return@sc
206216
}
207217
}

kotlinx-coroutines-core/common/test/sync/MutexTest.kt

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44

55
package kotlinx.coroutines.sync
66

7+
import kotlinx.atomicfu.*
78
import kotlinx.coroutines.*
89
import kotlin.test.*
910

1011
class MutexTest : TestBase() {
12+
private val enterCount = atomic(0)
13+
private val releasedCount = atomic(0)
14+
1115
@Test
1216
fun testSimple() = runTest {
1317
val mutex = Mutex()
@@ -106,4 +110,35 @@ class MutexTest : TestBase() {
106110
assertFalse(mutex.holdsLock(firstOwner))
107111
assertFalse(mutex.holdsLock(secondOwner))
108112
}
109-
}
113+
114+
@Test
115+
fun cancelLock() = runTest() {
116+
val mutex = Mutex()
117+
enterCount.value = 0
118+
releasedCount.value = 0
119+
repeat(1000) {
120+
val job = launch(Dispatchers.Default) {
121+
val owner = Any()
122+
try {
123+
enterCount.incrementAndGet()
124+
mutex.withLock(owner) {}
125+
// repeat to give an increase in race probability
126+
mutex.withLock(owner) {}
127+
} finally {
128+
// should be no way lock is still held by owner here
129+
if (mutex.holdsLock(owner)) {
130+
// if it is held, ensure test case doesn't lockup
131+
mutex.unlock(owner)
132+
} else {
133+
releasedCount.incrementAndGet()
134+
}
135+
}
136+
}
137+
mutex.withLock {
138+
job.cancel()
139+
}
140+
job.join()
141+
}
142+
assertEquals(enterCount.value, releasedCount.value)
143+
}
144+
}

0 commit comments

Comments
 (0)