Skip to content

Commit 4fe809f

Browse files
qwwdfsadTilps
andauthored
Unlock Mutex and release Semaphore during cancellation on a fast branch of slow-path in Mutex/Semaphore (#2396)
Fixes #2390 Co-authored-by: Gareth Pearce <[email protected]>
1 parent 8ca5296 commit 4fe809f

File tree

5 files changed

+43
-8
lines changed

5 files changed

+43
-8
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ 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+
// TODO implement functional type in LockCont as soon as we get rid of legacy JS
205+
cont.resume(Unit) { unlock(owner) }
205206
return@sc
206207
}
207208
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Se
172172
if (addAcquireToQueue(cont)) return@sc
173173
val p = _availablePermits.getAndDecrement()
174174
if (p > 0) { // permit acquired
175-
cont.resume(Unit)
175+
cont.resume(Unit, onCancellationRelease)
176176
return@sc
177177
}
178178
}
@@ -206,9 +206,8 @@ private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Se
206206
// On CAS failure -- the cell must be either PERMIT or BROKEN
207207
// If the cell already has PERMIT from tryResumeNextFromQueue, try to grab it
208208
if (segment.cas(i, PERMIT, TAKEN)) { // took permit thus eliminating acquire/release pair
209-
// The following resume must always succeed, since continuation was not published yet and we don't have
210-
// to pass onCancellationRelease handle, since the coroutine did not suspend yet and cannot be cancelled
211-
cont.resume(Unit)
209+
/// This continuation is not yet published, but still can be cancelled via outer job
210+
cont.resume(Unit, onCancellationRelease)
212211
return true
213212
}
214213
assert { segment.get(i) === BROKEN } // it must be broken in this case, no other way around it

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package kotlinx.coroutines.sync
66

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

@@ -106,4 +107,4 @@ class MutexTest : TestBase() {
106107
assertFalse(mutex.holdsLock(firstOwner))
107108
assertFalse(mutex.holdsLock(secondOwner))
108109
}
109-
}
110+
}

kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,21 @@ class MutexStressTest : TestBase() {
9090
}
9191
}
9292
}
93-
}
93+
94+
@Test
95+
fun testShouldBeUnlockedOnCancellation() = runTest {
96+
val mutex = Mutex()
97+
val n = 1000 * stressTestMultiplier
98+
repeat(n) {
99+
val job = launch(Dispatchers.Default) {
100+
mutex.lock()
101+
mutex.unlock()
102+
}
103+
mutex.withLock {
104+
job.cancel()
105+
}
106+
job.join()
107+
assertFalse { mutex.isLocked }
108+
}
109+
}
110+
}

kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package kotlinx.coroutines.sync
22

33
import kotlinx.coroutines.*
44
import org.junit.Test
5-
import kotlin.test.assertEquals
5+
import kotlin.test.*
66

77
class SemaphoreStressTest : TestBase() {
88
@Test
@@ -90,4 +90,21 @@ class SemaphoreStressTest : TestBase() {
9090
}
9191
}
9292
}
93+
94+
@Test
95+
fun testShouldBeUnlockedOnCancellation() = runTest {
96+
val semaphore = Semaphore(1)
97+
val n = 1000 * stressTestMultiplier
98+
repeat(n) {
99+
val job = launch(Dispatchers.Default) {
100+
semaphore.acquire()
101+
semaphore.release()
102+
}
103+
semaphore.withPermit {
104+
job.cancel()
105+
}
106+
job.join()
107+
assertTrue { semaphore.availablePermits == 1 }
108+
}
109+
}
93110
}

0 commit comments

Comments
 (0)