Skip to content

Commit 71dfd4b

Browse files
committed
PR Review comments: add user friendly docs for public api, delegate to ReentrantLock on JVM, inline and private funs where possible. Additionally removed legacy NativeNode.
1 parent 9b08837 commit 71dfd4b

File tree

16 files changed

+133
-182
lines changed

16 files changed

+133
-182
lines changed

atomicfu/api/atomicfu.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ public final class kotlinx/atomicfu/TraceKt {
137137

138138
public final class kotlinx/atomicfu/locks/Mutex {
139139
public fun <init> ()V
140+
public fun <init> (Ljava/util/concurrent/locks/ReentrantLock;)V
141+
public final fun getReentrantLock ()Ljava/util/concurrent/locks/ReentrantLock;
140142
public final fun isLocked ()Z
141143
public final fun lock ()V
142144
public final fun tryLock ()Z

atomicfu/src/androidNative32BitMain/kotlin/kotlinx/atomicfu/locks/NativeMutexNode.kt

Lines changed: 0 additions & 31 deletions
This file was deleted.

atomicfu/src/androidNative64BitMain/kotlin/kotlinx/atomicfu/locks/NativeMutexNode.kt

Lines changed: 0 additions & 31 deletions
This file was deleted.

atomicfu/src/commonMain/kotlin/kotlinx/atomicfu/locks/Mutex.kt

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,70 @@ import kotlin.contracts.InvocationKind
55
import kotlin.contracts.contract
66

77
/**
8-
* Multiplatform mutex.
9-
* On native based on pthread system calls.
10-
* On JVM delegates to ReentrantLock.
8+
* Mutual exclusion for Kotlin Multiplatform.
9+
*
10+
* It can protect a shared resource or critical section from multiple thread accesses.
11+
* Threads can acquire the lock by calling [lock] and release the lock by calling [unlock].
12+
*
13+
* When a thread calls [lock] while another thread is locked, it will suspend until the lock is released.
14+
* When multiple threads are waiting for the lock, they will acquire it in a fair order (first in first out).
15+
*
16+
* It is reentrant, meaning the lock holding thread can call [lock] multiple times without suspending.
17+
* To release the lock (after multiple [lock] calls) an equal number of [unlock] calls are required.
18+
*
19+
* This Mutex should not be used in combination with coroutines and `suspend` functions
20+
* as it blocks the waiting thread.
21+
* Use the `Mutex` from the coroutines library instead.
22+
*
23+
* ```Kotlin
24+
* mutex.withLock {
25+
* // Critical section only executed by
26+
* // one thread at a time.
27+
* }
28+
* ```
1129
*/
1230
expect class Mutex() {
31+
/**
32+
* Returns `true` if this mutex is locked.
33+
*/
1334
fun isLocked(): Boolean
14-
fun tryLock(): Boolean
15-
fun lock()
35+
36+
/**
37+
* Tries to lock this mutex, returning `false` if this mutex is already locked.
38+
*
39+
* It is recommended to use [withLock] for safety reasons, so that the acquired lock is always
40+
* released at the end of your critical section, and [unlock] is never invoked before a successful
41+
* lock acquisition.
42+
*/
43+
fun tryLock(): Boolean
44+
45+
/**
46+
* Locks the mutex, suspends the thread until the lock is acquired.
47+
*
48+
* It is recommended to use [withLock] for safety reasons, so that the acquired lock is always
49+
* released at the end of your critical section, and [unlock] is never invoked before a successful
50+
* lock acquisition.
51+
*/
52+
fun lock()
53+
54+
/**
55+
* Releases the lock.
56+
* Throws [IllegalStateException] when the current thread is not holding the lock.
57+
*
58+
* It is recommended to use [withLock] for safety reasons, so that the acquired lock is always
59+
* released at the end of the critical section, and [unlock] is never invoked before a successful
60+
* lock acquisition.
61+
*/
1662
fun unlock()
1763
}
1864

65+
/**
66+
* Executes the given code [block] under this mutex's lock.
67+
*
68+
* @return result of [block]
69+
*/
1970
@OptIn(ExperimentalContracts::class)
20-
fun <T> Mutex.withLock(block: () -> T): T {
71+
inline fun <T> Mutex.withLock(block: () -> T): T {
2172
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
2273
lock()
2374
return try {

atomicfu/src/concurrentMain/kotlin/kotlinx/atomicfu/locks/Mutex.kt

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,40 @@ import kotlinx.atomicfu.atomic
55
import kotlinx.atomicfu.parking.ThreadParker
66
import kotlinx.atomicfu.parking.currentThreadId
77

8-
/**
9-
* Mutex implementation for Kotlin/Native.
10-
*
11-
* The [state] variable stands for: 0 -> Lock is free
12-
* 1 -> Lock is locked but no waiters
13-
* 4 -> Lock is locked with 3 waiters
14-
*
15-
* The state.incrementAndGet() call makes my claim on the lock.
16-
* The returned value either means I acquired it (when it is 1).
17-
* Or I need to enqueue and park (when it is > 1).
18-
*
19-
* The [holdCount] variable is to enable reentrancy.
20-
*
21-
* Works by using a [parkingQueue].
22-
* When a thread tries to acquire the lock, but finds it is already locked it enqueues by appending to the [parkingQueue].
23-
* On enqueue the parking queue provides the second last node, this node is used to park on.
24-
* When our thread is woken up that means that the thread parked on the thrid last node called unpark on the second last node.
25-
* Since a woken up thread is first inline it means that it's node is the head and can therefore dequeue.
26-
*
27-
* Unlocking happens by calling state.decrementAndGet().
28-
* When the returned value is 0 it means the lock is free and we can simply return.
29-
* If the new state is > 0, then there are waiters. We wake up the first by unparking the head of the queue.
30-
* This even works when a thread is not parked yet,
31-
* since the ThreadParker can be pre-unparked resulting in the parking call to return immediately.
32-
*/
33-
actual class Mutex {
8+
internal class NativeMutex {
9+
/**
10+
* Mutex implementation for Kotlin/Native.
11+
* In concurrentMain sourceSet to be testable with Lincheck.
12+
*
13+
* The [state] variable stands for: 0 -> Lock is free
14+
* 1 -> Lock is locked but no waiters
15+
* 4 -> Lock is locked with 3 waiters
16+
*
17+
* The state.incrementAndGet() call makes my claim on the lock.
18+
* The returned value either means I acquired it (when it is 1).
19+
* Or I need to enqueue and park (when it is > 1).
20+
*
21+
* The [holdCount] variable is to enable reentrancy.
22+
*
23+
* Works by using a [parkingQueue].
24+
* When a thread tries to acquire the lock, but finds it is already locked it enqueues by appending to the [parkingQueue].
25+
* On enqueue the parking queue provides the second last node, this node is used to park on.
26+
* When our thread is woken up that means that the thread parked on the thrid last node called unpark on the second last node.
27+
* Since a woken up thread is first inline it means that it's node is the head and can therefore dequeue.
28+
*
29+
* Unlocking happens by calling state.decrementAndGet().
30+
* When the returned value is 0 it means the lock is free and we can simply return.
31+
* If the new state is > 0, then there are waiters. We wake up the first by unparking the head of the queue.
32+
* This even works when a thread is not parked yet,
33+
* since the ThreadParker can be pre-unparked resulting in the parking call to return immediately.
34+
*/
3435
private val parkingQueue = ParkingQueue()
3536
private val owningThread = atomic(-1L)
3637
private val state = atomic(0)
3738
private val holdCount = atomic(0)
3839

3940

40-
actual fun lock() {
41+
fun lock() {
4142
val currentThreadId = currentThreadId()
4243

4344
// Has to be checked in this order!
@@ -69,7 +70,7 @@ actual class Mutex {
6970
}
7071
}
7172

72-
actual fun unlock() {
73+
fun unlock() {
7374
val currentThreadId = currentThreadId()
7475
val currentOwnerId = owningThread.value
7576
if (currentThreadId != currentOwnerId) throw IllegalStateException("Thread is not holding the lock")
@@ -91,11 +92,11 @@ actual class Mutex {
9192
}
9293
}
9394

94-
actual fun isLocked(): Boolean {
95+
fun isLocked(): Boolean {
9596
return state.value > 0
9697
}
9798

98-
actual fun tryLock(): Boolean {
99+
fun tryLock(): Boolean {
99100
val currentThreadId = currentThreadId()
100101
if (holdCount.value > 0 && owningThread.value == currentThreadId || state.compareAndSet(0, 1)) {
101102
owningThread.value = currentThreadId
@@ -106,7 +107,7 @@ actual class Mutex {
106107
}
107108

108109
// Based on Micheal-Scott Queue
109-
internal inner class ParkingQueue {
110+
private class ParkingQueue {
110111
private val head: AtomicRef<Node>
111112
private val tail: AtomicRef<Node>
112113

@@ -142,7 +143,7 @@ actual class Mutex {
142143

143144
}
144145

145-
internal inner class Node {
146+
private class Node {
146147
val parker = ThreadParker()
147148
val next = atomic<Node?>(null)
148149
}

atomicfu/src/concurrentTest/kotlin/kotlinx/atomicfu/locks/NativeMutexTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class NativeMutexTest {
1010

1111
@Test
1212
fun testNativeMutexSlow() {
13-
val mutex = Mutex()
13+
val mutex = NativeMutex()
1414
val resultList = mutableListOf<String>()
1515

1616
val fut1 = testThread {

atomicfu/src/concurrentTest/kotlin/kotlinx/atomicfu/locks/ReentrancyTests.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class ReentrancyTests {
77

88
@Test
99
fun reentrantTestSuccess() {
10-
val lock = Mutex()
10+
val lock = NativeMutex()
1111
lock.lock()
1212
lock.lock()
1313
lock.unlock()
@@ -16,7 +16,7 @@ class ReentrancyTests {
1616

1717
@Test
1818
fun reentrantTestFail() {
19-
val lock = Mutex()
19+
val lock = NativeMutex()
2020
lock.lock()
2121
lock.lock()
2222
lock.unlock()

atomicfu/src/concurrentTest/kotlin/kotlinx/atomicfu/locks/VaryingContentionTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class VaryingContentionTest {
5454
)
5555

5656
class LockInt {
57-
private val lock = Mutex()
57+
private val lock = NativeMutex()
5858
private val check = atomic(0)
5959
var n = 0
6060
fun lock() {

atomicfu/src/jsAndWasmSharedMain/kotlin/kotlinx/atomicfu/locks/Mutex.jsAndWasmShared.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package kotlinx.atomicfu.locks
22

33
/**
4-
* Multiplatform mutex.
5-
* On native based on pthread system calls.
6-
* On JVM delegates to ReentrantLock.
4+
* Part of multiplatform mutex.
5+
* Since this mutex will run in a single threaded environment, it doesn't provide any real synchronization.
6+
*
7+
* It does keep track of reentrancy.
78
*/
89
actual class Mutex {
910
private var state = 0
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package kotlinx.atomicfu.locks
2+
3+
/**
4+
* This mutex uses a [ReentrantLock].
5+
*
6+
* [getReentrantLock] obtains the actual [ReentrantLock].
7+
* Construct with `Mutex(reentrantLock)` to create a [Mutex] that uses an existing instance of [ReentrantLock].
8+
*/
9+
actual class Mutex(private val reentrantLock: java.util.concurrent.locks.ReentrantLock) {
10+
actual constructor(): this(ReentrantLock())
11+
actual fun isLocked(): Boolean = reentrantLock.isLocked
12+
actual fun tryLock(): Boolean = reentrantLock.tryLock()
13+
actual fun lock() = reentrantLock.lock()
14+
actual fun unlock() = reentrantLock.unlock()
15+
16+
/**
17+
* @return the underlying [ReentrantLock]
18+
*/
19+
fun getReentrantLock(): ReentrantLock = reentrantLock
20+
}

atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/locks/NativeMutexLincheckReentrantTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import kotlinx.atomicfu.locks.Mutex
1+
import kotlinx.atomicfu.locks.NativeMutex
22
import org.jetbrains.kotlinx.lincheck.LoggingLevel
33
import org.jetbrains.kotlinx.lincheck.annotations.Operation
44
import org.jetbrains.kotlinx.lincheck.check
@@ -13,7 +13,7 @@ class NativeMutexLincheckReentrantTest {
1313
fun inc(): Int = ++value
1414
fun get() = value
1515
}
16-
private val lock = Mutex()
16+
private val lock = NativeMutex()
1717
private val counter = Counter()
1818

1919
@Test

atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/locks/NativeMutexLincheckTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import kotlinx.atomicfu.locks.Mutex
1+
import kotlinx.atomicfu.locks.NativeMutex
22
import org.jetbrains.kotlinx.lincheck.LoggingLevel
33
import org.jetbrains.kotlinx.lincheck.annotations.Operation
44
import org.jetbrains.kotlinx.lincheck.check
@@ -13,7 +13,7 @@ class NativeMutexLincheckTest {
1313
fun inc(): Int = ++value
1414
fun get() = value
1515
}
16-
private val lock = Mutex()
16+
private val lock = NativeMutex()
1717
private val counter = Counter()
1818

1919
@Test

atomicfu/src/mingwMain/kotlin/kotlinx/atomicfu/locks/NativeMutexNode.kt

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)