Skip to content

Commit 43b6be5

Browse files
committed
Introduce SegmentQueueSynchronizer abstraction for synchronization primitives and ReadWriteMutex
Signed-off-by: Nikita Koval <[email protected]>
1 parent 2f8744c commit 43b6be5

File tree

6 files changed

+250
-62
lines changed

6 files changed

+250
-62
lines changed

Diff for: kotlinx-coroutines-core/common/src/internal/SegmentQueueSynchronizer.kt

+57-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package kotlinx.coroutines.internal
66

77
import kotlinx.atomicfu.*
88
import kotlinx.coroutines.*
9+
import kotlinx.coroutines.channels.*
910
import kotlinx.coroutines.internal.SegmentQueueSynchronizer.*
1011
import kotlinx.coroutines.internal.SegmentQueueSynchronizer.CancellationMode.*
1112
import kotlinx.coroutines.internal.SegmentQueueSynchronizer.ResumeMode.*
@@ -208,6 +209,42 @@ internal abstract class SegmentQueueSynchronizer<T : Any> {
208209
returnValue(value)
209210
}
210211

212+
internal fun suspendCancelled(): T? {
213+
// Increment `suspendIdx` and find the segment
214+
// with the corresponding id. It is guaranteed
215+
// that this segment is not removed since at
216+
// least the cell for this `suspend` invocation
217+
// is not in the `CANCELLED` state.
218+
val curSuspendSegm = this.suspendSegment.value
219+
val suspendIdx = suspendIdx.getAndIncrement()
220+
val segment = this.suspendSegment.findSegmentAndMoveForward(id = suspendIdx / SEGMENT_SIZE, startFrom = curSuspendSegm,
221+
createNewSegment = ::createSegment).segment
222+
assert { segment.id == suspendIdx / SEGMENT_SIZE }
223+
// Try to install the waiter into the cell - this is the regular path.
224+
val i = (suspendIdx % SEGMENT_SIZE).toInt()
225+
if (segment.cas(i, null, CANCELLED)) {
226+
// The continuation is successfully installed, and
227+
// `resume` cannot break the cell now, so this
228+
// suspension is successful.
229+
// Add a cancellation handler if required and finish.
230+
return null
231+
}
232+
// The continuation installation has failed. This happened because a concurrent
233+
// `resume` came earlier to this cell and put its value into it. Remember that
234+
// in the `SYNC` resumption mode this concurrent `resume` can mark the cell as broken.
235+
//
236+
// Try to grab the value if the cell is not in the `BROKEN` state.
237+
val value = segment.get(i)
238+
if (value !== BROKEN && segment.cas(i, value, TAKEN)) {
239+
// The elimination is performed successfully,
240+
// complete with the value stored in the cell.
241+
return value as T
242+
}
243+
// The cell is broken, this can happen only in the `SYNC` resumption mode.
244+
assert { resumeMode == SYNC && segment.get(i) === BROKEN }
245+
return null
246+
}
247+
211248
@Suppress("UNCHECKED_CAST")
212249
internal fun suspend(waiter: Waiter): Boolean {
213250
// Increment `suspendIdx` and find the segment
@@ -359,17 +396,30 @@ internal abstract class SegmentQueueSynchronizer<T : Any> {
359396
return TRY_RESUME_SUCCESS
360397
}
361398
// Does the cell store a cancellable continuation?
362-
cellState is CancellableContinuation<*> -> {
399+
cellState is Waiter -> {
363400
// Change the cell state to `RESUMED`, so
364401
// the cancellation handler cannot be invoked
365402
// even if the continuation becomes cancelled.
366403
if (!segment.cas(i, cellState, RESUMED)) continue@modify_cell
367404
// Try to resume the continuation.
368-
val token = (cellState as CancellableContinuation<T>).tryResume(value, null, { returnValue(value) })
369-
if (token != null) {
370-
// Hooray, the continuation is successfully resumed!
371-
cellState.completeResume(token)
372-
} else {
405+
val resumed = when(cellState) {
406+
is CancellableContinuation<*> -> {
407+
(cellState as CancellableContinuation<T>)
408+
val token = cellState.tryResume(value, null, { returnValue(value) })
409+
if (token != null) {
410+
// Hooray, the continuation is successfully resumed!
411+
cellState.completeResume(token)
412+
true
413+
} else {
414+
false
415+
}
416+
}
417+
is SelectInstance<*> -> {
418+
cellState.trySelect(this@SegmentQueueSynchronizer, value)
419+
}
420+
else -> error("unexpected")
421+
}
422+
if (!resumed) {
373423
// Unfortunately, the continuation resumption has failed.
374424
// Fail the current `resume` if the simple cancellation mode is used.
375425
if (cancellationMode === SIMPLE)
@@ -552,7 +602,7 @@ internal abstract class SegmentQueueSynchronizer<T : Any> {
552602
val cellState = get(index)
553603
when {
554604
cellState === RESUMED -> return false
555-
cellState is CancellableContinuation<*> -> {
605+
cellState is Waiter -> {
556606
if (cas(index, cellState, CANCELLING)) return true
557607
}
558608
else -> {

Diff for: kotlinx-coroutines-core/common/src/sync/Mutex.kt

+124-27
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import kotlinx.coroutines.*
99
import kotlinx.coroutines.internal.*
1010
import kotlinx.coroutines.selects.*
1111
import kotlin.contracts.*
12+
import kotlin.coroutines.*
1213
import kotlin.jvm.*
1314

1415
/**
@@ -131,7 +132,7 @@ public suspend inline fun <T> Mutex.withLock(owner: Any? = null, action: () -> T
131132
}
132133

133134

134-
internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 else 0), Mutex {
135+
internal open class MutexImpl(locked: Boolean) : SegmentQueueSynchronizer<Unit>(), Mutex {
135136
/**
136137
* After the lock is acquired, the corresponding owner is stored in this field.
137138
* The [unlock] operation checks the owner and either re-sets it to [NO_OWNER],
@@ -140,13 +141,15 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1
140141
*/
141142
private val owner = atomic<Any?>(if (locked) null else NO_OWNER)
142143

144+
private val availablePermits = atomic(if (locked) 0 else 1)
145+
143146
private val onSelectCancellationUnlockConstructor: OnCancellationConstructor =
144147
{ _: SelectInstance<*>, owner: Any?, _: Any? ->
145148
{ unlock(owner) }
146149
}
147150

148151
override val isLocked: Boolean get() =
149-
availablePermits == 0
152+
availablePermits.value <= 0
150153

151154
override fun holdsLock(owner: Any): Boolean {
152155
while (true) {
@@ -161,13 +164,84 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1
161164
}
162165

163166
override suspend fun lock(owner: Any?) {
164-
if (tryLock(owner)) return
167+
// if (tryLock(owner)) return
165168
lockSuspend(owner)
166169
}
167170

168-
private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable { cont ->
171+
private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable<Unit> { cont ->
172+
cont as CancellableContinuationImpl<Unit>
169173
val contWithOwner = CancellableContinuationWithOwner(cont, owner)
170-
acquire(contWithOwner)
174+
lockImpl(contWithOwner, owner)
175+
}
176+
177+
private fun lockImpl(waiter: Waiter, owner: Any?) {
178+
xxx@ while (true) {
179+
// Get the current number of available permits.
180+
val p = availablePermits.getAndDecrement()
181+
// Try to decrement the number of available
182+
// permits if it is greater than zero.
183+
if (p <= 0) {
184+
// The semaphore permit acquisition has failed.
185+
// However, we need to check that this mutex is not
186+
// locked by our owner.
187+
if (owner != null) {
188+
// Is this mutex locked by our owner?
189+
var curOwner = this.owner.value
190+
191+
if (curOwner === owner) {
192+
if (suspendCancelled() != null) release()
193+
when (waiter) {
194+
is CancellableContinuation<*> -> {
195+
waiter.resumeWithException(IllegalStateException("ERROR"))
196+
}
197+
is SelectInstance<*> -> {
198+
waiter.selectInRegistrationPhase(ON_LOCK_ALREADY_LOCKED_BY_OWNER)
199+
}
200+
}
201+
return
202+
}
203+
204+
while (curOwner === NO_OWNER) {
205+
curOwner = this.owner.value
206+
if (!isLocked) {
207+
if (suspendCancelled() != null) release()
208+
continue@xxx
209+
}
210+
}
211+
if (curOwner === owner) {
212+
if (suspendCancelled() != null) release()
213+
when (waiter) {
214+
is CancellableContinuation<*> -> {
215+
waiter.resumeWithException(IllegalStateException("ERROR"))
216+
}
217+
is SelectInstance<*> -> {
218+
waiter.selectInRegistrationPhase(ON_LOCK_ALREADY_LOCKED_BY_OWNER)
219+
}
220+
}
221+
return
222+
}
223+
// This mutex is either locked by another owner or unlocked.
224+
// In the latter case, it is possible that it WAS locked by
225+
// our owner when the semaphore permit acquisition has failed.
226+
// To preserve linearizability, the operation restarts in this case.
227+
// if (!isLocked) continuex
228+
}
229+
if (suspend(waiter)) return
230+
} else {
231+
assert { p == 1 }
232+
assert { this.owner.value === NO_OWNER }
233+
when (waiter) {
234+
is CancellableContinuation<*> -> {
235+
waiter as CancellableContinuation<Unit>
236+
waiter.resume(Unit, null)
237+
}
238+
is SelectInstance<*> -> {
239+
waiter.selectInRegistrationPhase(Unit)
240+
}
241+
}
242+
return
243+
}
244+
}
171245
}
172246

173247
override fun tryLock(owner: Any?): Boolean = when (tryLockImpl(owner)) {
@@ -179,25 +253,27 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1
179253

180254
private fun tryLockImpl(owner: Any?): Int {
181255
while (true) {
182-
if (tryAcquire()) {
183-
assert { this.owner.value === NO_OWNER }
184-
this.owner.value = owner
185-
return TRY_LOCK_SUCCESS
186-
} else {
256+
// Get the current number of available permits.
257+
val p = availablePermits.value
258+
// Try to decrement the number of available
259+
// permits if it is greater than zero.
260+
if (p <= 0) {
187261
// The semaphore permit acquisition has failed.
188262
// However, we need to check that this mutex is not
189263
// locked by our owner.
190264
if (owner != null) {
191265
// Is this mutex locked by our owner?
192-
if (holdsLock(owner)) return TRY_LOCK_ALREADY_LOCKED_BY_OWNER
193-
// This mutex is either locked by another owner or unlocked.
194-
// In the latter case, it is possible that it WAS locked by
195-
// our owner when the semaphore permit acquisition has failed.
196-
// To preserve linearizability, the operation restarts in this case.
197-
if (!isLocked) continue
266+
val curOwner = this.owner.value
267+
if (curOwner === NO_OWNER) continue
268+
if (curOwner === owner) return TRY_LOCK_ALREADY_LOCKED_BY_OWNER
198269
}
199270
return TRY_LOCK_FAILED
200271
}
272+
if (availablePermits.compareAndSet(p, p - 1)) {
273+
assert { this.owner.value === NO_OWNER }
274+
this.owner.value = owner
275+
return TRY_LOCK_SUCCESS
276+
}
201277
}
202278
}
203279

@@ -218,6 +294,27 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1
218294
}
219295
}
220296

297+
fun release() {
298+
while (true) {
299+
// Increment the number of available permits.
300+
val p = availablePermits.value
301+
// Is this `release` call correct and does not
302+
// exceed the maximal number of permits?
303+
if (p >= 1) {
304+
error("This mutex is not locked")
305+
}
306+
if (availablePermits.compareAndSet(p, p + 1)) {
307+
// Is there a waiter that should be resumed?
308+
if (p == 0) return
309+
// Try to resume the first waiter, and
310+
// restart the operation if either this
311+
// first waiter is cancelled or
312+
// due to `SYNC` resumption mode.
313+
if (resume(Unit)) return
314+
}
315+
}
316+
}
317+
221318
@Suppress("UNCHECKED_CAST", "OverridingDeprecatedMember", "OVERRIDE_DEPRECATION")
222319
override val onLock: SelectClause2<Any?, Mutex> get() = SelectClause2Impl(
223320
clauseObject = this,
@@ -227,11 +324,7 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1
227324
)
228325

229326
protected open fun onLockRegFunction(select: SelectInstance<*>, owner: Any?) {
230-
if (owner != null && holdsLock(owner)) {
231-
select.selectInRegistrationPhase(ON_LOCK_ALREADY_LOCKED_BY_OWNER)
232-
} else {
233-
onAcquireRegFunction(SelectInstanceWithOwner(select, owner), owner)
234-
}
327+
lockImpl(SelectInstanceWithOwner(select as SelectInstanceInternal<*>, owner), owner)
235328
}
236329

237330
protected open fun onLockProcessResult(owner: Any?, result: Any?): Any? {
@@ -243,10 +336,10 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1
243336

244337
private inner class CancellableContinuationWithOwner(
245338
@JvmField
246-
val cont: CancellableContinuation<Unit>,
339+
val cont: CancellableContinuationImpl<Unit>,
247340
@JvmField
248341
val owner: Any?
249-
) : CancellableContinuation<Unit> by cont {
342+
) : CancellableContinuation<Unit> by cont, Waiter by cont {
250343
override fun tryResume(value: Unit, idempotent: Any?, onCancellation: ((cause: Throwable) -> Unit)?): Any? {
251344
assert { this@MutexImpl.owner.value === NO_OWNER }
252345
val token = cont.tryResume(value, idempotent) {
@@ -270,10 +363,10 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1
270363

271364
private inner class SelectInstanceWithOwner<Q>(
272365
@JvmField
273-
val select: SelectInstance<Q>,
366+
val select: SelectInstanceInternal<Q>,
274367
@JvmField
275368
val owner: Any?
276-
) : SelectInstanceInternal<Q> by select as SelectInstanceInternal<Q> {
369+
) : SelectInstanceInternal<Q> by select {
277370
override fun trySelect(clauseObject: Any, result: Any?): Boolean {
278371
assert { this@MutexImpl.owner.value === NO_OWNER }
279372
return select.trySelect(clauseObject, result).also { success ->
@@ -282,12 +375,16 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1
282375
}
283376

284377
override fun selectInRegistrationPhase(internalResult: Any?) {
285-
assert { this@MutexImpl.owner.value === NO_OWNER }
286-
this@MutexImpl.owner.value = owner
378+
if (internalResult !== ON_LOCK_ALREADY_LOCKED_BY_OWNER) {
379+
assert { this@MutexImpl.owner.value === NO_OWNER }
380+
this@MutexImpl.owner.value = owner
381+
}
287382
select.selectInRegistrationPhase(internalResult)
288383
}
289384
}
290385

386+
internal val debugStateRepresentation: String get() = "p=${availablePermits.value},owner=${owner.value},SQS=${super.toString()}"
387+
291388
override fun toString() = "Mutex@${hexAddress}[isLocked=$isLocked,owner=${owner.value}]"
292389
}
293390

Diff for: kotlinx-coroutines-core/common/src/sync/ReadWriteMutex.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ internal class ReadWriteMutexImpl : ReadWriteMutex, Mutex {
266266
// The number of waiting readers was incremented
267267
// correctly, wait for a reader lock in `sqsReaders`.
268268
suspendCancellableCoroutineReusable<Unit> { cont ->
269-
sqsReaders.suspend(cont)
269+
sqsReaders.suspend(cont as Waiter)
270270
}
271271
return
272272
} else {
@@ -286,7 +286,7 @@ internal class ReadWriteMutexImpl : ReadWriteMutex, Mutex {
286286
// when this concurrent `write.unlock()` completes.
287287
if (wr == 0) {
288288
suspendCancellableCoroutineReusable<Unit> { cont ->
289-
sqsReaders.suspend(cont)
289+
sqsReaders.suspend(cont as Waiter)
290290
}
291291
return
292292
}
@@ -399,7 +399,7 @@ internal class ReadWriteMutexImpl : ReadWriteMutex, Mutex {
399399
// Try to increment the number of waiting writers and suspend in `sqsWriters`.
400400
if (state.compareAndSet(s, state(s.ar, s.wla, s.ww + 1, s.rwr))) {
401401
suspendCancellableCoroutineReusable<Unit> { cont ->
402-
sqsWriters.suspend(cont)
402+
sqsWriters.suspend(cont as Waiter)
403403
}
404404
return
405405
}

Diff for: kotlinx-coroutines-core/common/src/sync/Semaphore.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -156,18 +156,18 @@ internal open class SemaphoreImpl(
156156
}
157157
}
158158

159-
private suspend fun acquireSlowPath() = suspendCancellableCoroutineReusable<Unit> sc@{ cont ->
159+
private suspend fun acquireSlowPath() = suspendCancellableCoroutineReusable sc@{ cont ->
160160
while (true) {
161161
// Try to suspend.
162-
if (suspend(cont)) return@sc
162+
if (suspend(cont as Waiter)) return@sc
163163
// The suspension has been failed
164164
// due to the synchronous resumption mode.
165165
// Restart the whole `acquire`, and decrement
166166
// the number of available permits at first.
167167
val p = decPermits()
168168
// Is the permit acquired?
169169
if (p > 0) {
170-
cont.resume(Unit)
170+
cont.resume(Unit) { release() }
171171
return@sc
172172
}
173173
// Permit has not been acquired, go to

0 commit comments

Comments
 (0)