Skip to content

Commit 54d5f05

Browse files
committed
Introduce SegmentQueueSynchronizer abstraction for synchronization primitives and ReadWriteMutex
1 parent ea440c5 commit 54d5f05

15 files changed

+2731
-207
lines changed

Diff for: .idea/dictionaries/shared.xml

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt

+5-6
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import org.openjdk.jmh.annotations.*
1414
import java.util.concurrent.ForkJoinPool
1515
import java.util.concurrent.TimeUnit
1616

17-
@Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MICROSECONDS)
18-
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MICROSECONDS)
17+
@Warmup(iterations = 2, time = 500, timeUnit = TimeUnit.MICROSECONDS)
18+
@Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MICROSECONDS)
1919
@Fork(value = 1)
2020
@BenchmarkMode(Mode.AverageTime)
2121
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@@ -31,7 +31,6 @@ open class SemaphoreBenchmark {
3131
private var _3_maxPermits: Int = 0
3232

3333
@Param("1", "2", "4") // local machine
34-
// @Param("1", "2", "4", "8", "16", "32", "64", "128", "144") // dasquad
3534
// @Param("1", "2", "4", "8", "16", "32", "64", "96") // Google Cloud
3635
private var _4_parallelism: Int = 0
3736

@@ -51,7 +50,7 @@ open class SemaphoreBenchmark {
5150
val semaphore = Semaphore(_3_maxPermits)
5251
val jobs = ArrayList<Job>(coroutines)
5352
repeat(coroutines) {
54-
jobs += GlobalScope.launch {
53+
jobs += GlobalScope.launch(dispatcher) {
5554
repeat(n) {
5655
semaphore.withPermit {
5756
doGeomDistrWork(WORK_INSIDE)
@@ -69,7 +68,7 @@ open class SemaphoreBenchmark {
6968
val semaphore = Channel<Unit>(_3_maxPermits)
7069
val jobs = ArrayList<Job>(coroutines)
7170
repeat(coroutines) {
72-
jobs += GlobalScope.launch {
71+
jobs += GlobalScope.launch(dispatcher) {
7372
repeat(n) {
7473
semaphore.send(Unit) // acquire
7574
doGeomDistrWork(WORK_INSIDE)
@@ -89,4 +88,4 @@ enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> Cor
8988

9089
private const val WORK_INSIDE = 80
9190
private const val WORK_OUTSIDE = 40
92-
private const val BATCH_SIZE = 1000000
91+
private const val BATCH_SIZE = 1_000_000

Diff for: kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

+12
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,18 @@ public final class kotlinx/coroutines/sync/MutexKt {
12711271
public static synthetic fun withLock$default (Lkotlinx/coroutines/sync/Mutex;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
12721272
}
12731273

1274+
public abstract interface class kotlinx/coroutines/sync/ReadWriteMutex {
1275+
public abstract fun getWrite ()Lkotlinx/coroutines/sync/Mutex;
1276+
public abstract fun readLock (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
1277+
public abstract fun readUnlock ()V
1278+
}
1279+
1280+
public final class kotlinx/coroutines/sync/ReadWriteMutexKt {
1281+
public static final fun ReadWriteMutex ()Lkotlinx/coroutines/sync/ReadWriteMutex;
1282+
public static final fun read (Lkotlinx/coroutines/sync/ReadWriteMutex;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
1283+
public static final fun write (Lkotlinx/coroutines/sync/ReadWriteMutex;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
1284+
}
1285+
12741286
public abstract interface class kotlinx/coroutines/sync/Semaphore {
12751287
public abstract fun acquire (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
12761288
public abstract fun getAvailablePermits ()I

Diff for: kotlinx-coroutines-core/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ static void configureJvmForLincheck(task) {
249249
task.maxHeapSize = '6g' // we may need more space for building an interleaving tree in the model checking mode
250250
task.jvmArgs = ['--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED', // required for transformation
251251
'--add-exports', 'java.base/jdk.internal.util=ALL-UNNAMED'] // in the model checking mode
252-
task.systemProperty 'kotlinx.coroutines.semaphore.segmentSize', '2'
253-
task.systemProperty 'kotlinx.coroutines.semaphore.maxSpinCycles', '1' // better for the model checking mode
252+
task.systemProperty 'kotlinx.coroutines.sqs.segmentSize', '2'
253+
task.systemProperty 'kotlinx.coroutines.sqs.maxSpinCycles', '1' // better for the model checking mode
254254
}
255255

256256
task jdk16Test(type: Test, dependsOn: [compileTestKotlinJvm, checkJdk16]) {

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

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ internal expect val DEBUG: Boolean
88
internal expect val Any.hexAddress: String
99
internal expect val Any.classSimpleName: String
1010
internal expect fun assert(value: () -> Boolean)
11+
internal inline fun assertNot(crossinline value: () -> Boolean) = assert { !value() }
1112

1213
/**
1314
* Throwable which can be cloned during stacktrace recovery in a class-specific way.

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

+13-10
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,13 @@ internal abstract class ConcurrentLinkedListNode<N : ConcurrentLinkedListNode<N>
150150
*/
151151
fun remove() {
152152
assert { removed } // The node should be logically removed at first.
153-
assert { !isTail } // The physical tail cannot be removed.
153+
// The physical tail cannot be removed. Instead, we remove it when
154+
// a new segment is added and this segment is not the tail one anymore.
155+
if (isTail) return
154156
while (true) {
155157
// Read `next` and `prev` pointers ignoring logically removed nodes.
156-
val prev = leftmostAliveNode
157-
val next = rightmostAliveNode
158+
val prev = aliveSegmentLeft
159+
val next = aliveSegmentRight
158160
// Link `next` and `prev`.
159161
next._prev.value = prev
160162
if (prev !== null) prev._next.value = next
@@ -166,17 +168,17 @@ internal abstract class ConcurrentLinkedListNode<N : ConcurrentLinkedListNode<N>
166168
}
167169
}
168170

169-
private val leftmostAliveNode: N? get() {
171+
private val aliveSegmentLeft: N? get() {
170172
var cur = prev
171173
while (cur !== null && cur.removed)
172174
cur = cur._prev.value
173175
return cur
174176
}
175177

176-
private val rightmostAliveNode: N get() {
178+
private val aliveSegmentRight: N get() {
177179
assert { !isTail } // Should not be invoked on the tail node
178180
var cur = next!!
179-
while (cur.removed)
181+
while (cur.removed && !cur.isTail)
180182
cur = cur.next!!
181183
return cur
182184
}
@@ -204,19 +206,20 @@ internal abstract class Segment<S : Segment<S>>(val id: Long, prev: S?, pointers
204206
* There are no pointers to this segment from outside, and
205207
* it is not a physical tail in the linked list of segments.
206208
*/
207-
override val removed get() = cleanedAndPointers.value == maxSlots && !isTail
209+
override val removed get() = cleanedAndPointers.value == maxSlots
208210

209211
// increments the number of pointers if this segment is not logically removed.
210-
internal fun tryIncPointers() = cleanedAndPointers.addConditionally(1 shl POINTERS_SHIFT) { it != maxSlots || isTail }
212+
internal fun tryIncPointers() = cleanedAndPointers.addConditionally(1 shl POINTERS_SHIFT) { it != maxSlots }
211213

212214
// returns `true` if this segment is logically removed after the decrement.
213-
internal fun decPointers() = cleanedAndPointers.addAndGet(-(1 shl POINTERS_SHIFT)) == maxSlots && !isTail
215+
internal fun decPointers() = cleanedAndPointers.addAndGet(-(1 shl POINTERS_SHIFT)) == maxSlots
214216

215217
/**
216218
* Invoked on each slot clean-up; should not be invoked twice for the same slot.
217219
*/
218220
fun onSlotCleaned() {
219-
if (cleanedAndPointers.incrementAndGet() == maxSlots && !isTail) remove()
221+
if (cleanedAndPointers.incrementAndGet() < maxSlots) return
222+
if (removed) remove()
220223
}
221224
}
222225

0 commit comments

Comments
 (0)