Skip to content

Commit f8fc478

Browse files
committed
Mutex is moved to sync subpackage
1 parent d4dcbe2 commit f8fc478

File tree

5 files changed

+89
-61
lines changed

5 files changed

+89
-61
lines changed

kotlinx-coroutines-core/README.md

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Coroutine builder functions:
88
| ------------- | ------------- | ---------------- | ---------------
99
| [launch] | [Job] | [CoroutineScope] | Launches coroutine that does not have any result
1010
| [async] | [Deferred] | [CoroutineScope] | Returns a single value with the future result
11-
| [produce] | [ProducerJob] | [ProducerScope] | Produces a stream of elements
11+
| [kotlinx.coroutines.experimental.channels.produce] | [kotlinx.coroutines.experimental.channels.ProducerJob] | [kotlinx.coroutines.experimental.channels.ProducerScope] | Produces a stream of elements
1212
| [runBlocking] | `T` | [CoroutineScope] | Blocks the thread while the coroutine runs
1313

1414
Coroutine dispatchers implementing [CoroutineDispatcher]:
@@ -25,8 +25,8 @@ Synchronization primitives for coroutines:
2525

2626
| **Name** | **Suspending functions** | **Description**
2727
| ---------- | ----------------------------------------------------------- | ---------------
28-
| [Mutex] | [lock][Mutex.lock] | Mutual exclusion
29-
| [Channel] | [send][SendChannel.send], [receive][ReceiveChannel.receive] | Communication channel (aka queue or exchanger)
28+
| [Mutex][kotlinx.coroutines.experimental.sync.Mutex] | [lock][kotlinx.coroutines.experimental.sync.Mutex.lock] | Mutual exclusion
29+
| [Channel][kotlinx.coroutines.experimental.channels.Channel] | [send][kotlinx.coroutines.experimental.channels.SendChannel.send], [receive][kotlinx.coroutines.experimental.channels.ReceiveChannel.receive] | Communication channel (aka queue or exchanger)
3030

3131
Top-level suspending functions:
3232

@@ -41,10 +41,10 @@ Top-level suspending functions:
4141

4242
| **Receiver** | **Suspending function** | **Select clause** | **Non-suspending version**
4343
| ---------------- | --------------------------------------------- | ------------------------------------------------ | --------------------------
44-
| [Deferred] | [await][Deferred.await] | [onAwait][SelectBuilder.onAwait] | [isCompleted][Deferred.isCompleted]
45-
| [SendChannel] | [send][SendChannel.send] | [onSend][SelectBuilder.onSend] | [offer][SendChannel.offer]
46-
| [ReceiveChannel] | [receive][ReceiveChannel.receive] | [onReceive][SelectBuilder.onReceive] | [poll][ReceiveChannel.poll]
47-
| [ReceiveChannel] | [receiveOrNull][ReceiveChannel.receiveOrNull] | [onReceiveOrNull][SelectBuilder.onReceiveOrNull] | [poll][ReceiveChannel.poll]
44+
| [Deferred] | [await][Deferred.await] | [onAwait][kotlinx.coroutines.experimental.selects.SelectBuilder.onAwait] | [isCompleted][Job.isCompleted]
45+
| [SendChannel][kotlinx.coroutines.experimental.channels.SendChannel] | [send][kotlinx.coroutines.experimental.channels.SendChannel.send] | [onSend][kotlinx.coroutines.experimental.selects.SelectBuilder.onSend] | [offer][kotlinx.coroutines.experimental.channels.SendChannel.offer]
46+
| [ReceiveChannel][kotlinx.coroutines.experimental.channels.ReceiveChannel] | [receive][kotlinx.coroutines.experimental.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.experimental.selects.SelectBuilder.onReceive] | [poll][kotlinx.coroutines.experimental.channels.ReceiveChannel.poll]
47+
| [ReceiveChannel][kotlinx.coroutines.experimental.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.experimental.channels.ReceiveChannel.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.experimental.selects.SelectBuilder.onReceiveOrNull] | [poll][kotlinx.coroutines.experimental.channels.ReceiveChannel.poll]
4848

4949
Cancellation support for user-defined suspending functions is available with [suspendCancellableCoroutine]
5050
helper function. [NonCancellable] job object is provided to suppress cancellation with
@@ -58,6 +58,10 @@ debugging facilities.
5858

5959
General-purpose coroutine builders, contexts, and helper functions.
6060

61+
# Package kotlinx.coroutines.experimental.sync
62+
63+
Synchronization primitives (mutex).
64+
6165
# Package kotlinx.coroutines.experimental.channels
6266

6367
Channels -- non-blocking primitives for communicating a stream of elements between coroutines.
@@ -81,30 +85,34 @@ Select expression to perform multiple suspending operations simultaneously until
8185
[newFixedThreadPoolContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-fixed-thread-pool-context.html
8286
[java.util.concurrent.Executor.toCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/to-coroutine-dispatcher.html
8387
[Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-unconfined/index.html
84-
[Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-mutex/index.html
85-
[Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/lock.html
8688
[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/delay.html
8789
[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/yield.html
8890
[run]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run.html
8991
[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/with-timeout.html
92+
[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/await.html
93+
[Job.isCompleted]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/is-completed.html
9094
[suspendCancellableCoroutine]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/suspend-cancellable-coroutine.html
9195
[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-non-cancellable/index.html
9296
[newCoroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-coroutine-context.html
97+
<!--- INDEX kotlinx.coroutines.experimental.sync -->
98+
[kotlinx.coroutines.experimental.sync.Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/index.html
99+
[kotlinx.coroutines.experimental.sync.Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/lock.html
93100
<!--- INDEX kotlinx.coroutines.experimental.channels -->
94-
[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.html
95-
[ProducerJob]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-producer-job/index.html
96-
[ProducerScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-producer-scope/index.html
97-
[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-channel/index.html
98-
[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/send.html
99-
[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/receive.html
100-
[SendChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/index.html
101-
[SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/offer.html
102-
[ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/index.html
103-
[ReceiveChannel.poll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/poll.html
104-
[ReceiveChannel.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/receive-or-null.html
101+
[kotlinx.coroutines.experimental.channels.produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.html
102+
[kotlinx.coroutines.experimental.channels.ProducerJob]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-producer-job/index.html
103+
[kotlinx.coroutines.experimental.channels.ProducerScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-producer-scope/index.html
104+
[kotlinx.coroutines.experimental.channels.Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-channel/index.html
105+
[kotlinx.coroutines.experimental.channels.SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/send.html
106+
[kotlinx.coroutines.experimental.channels.ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/receive.html
107+
[kotlinx.coroutines.experimental.channels.SendChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/index.html
108+
[kotlinx.coroutines.experimental.channels.SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/offer.html
109+
[kotlinx.coroutines.experimental.channels.ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-receive-channel/index.html
110+
[kotlinx.coroutines.experimental.channels.ReceiveChannel.poll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/poll.html
111+
[kotlinx.coroutines.experimental.channels.ReceiveChannel.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/receive-or-null.html
105112
<!--- INDEX kotlinx.coroutines.experimental.selects -->
106113
[kotlinx.coroutines.experimental.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/select.html
107-
[SelectBuilder.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/on-send.html
108-
[SelectBuilder.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/on-receive.html
109-
[SelectBuilder.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/on-receive-or-null.html
114+
[kotlinx.coroutines.experimental.selects.SelectBuilder.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/on-await.html
115+
[kotlinx.coroutines.experimental.selects.SelectBuilder.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/on-send.html
116+
[kotlinx.coroutines.experimental.selects.SelectBuilder.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/on-receive.html
117+
[kotlinx.coroutines.experimental.selects.SelectBuilder.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/on-receive-or-null.html
110118
<!--- END -->

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Mutex.kt renamed to kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/sync/Mutex.kt

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
package kotlinx.coroutines.experimental
17+
package kotlinx.coroutines.experimental.sync
1818

19+
import kotlinx.coroutines.experimental.*
1920
import kotlinx.coroutines.experimental.internal.LockFreeLinkedListHead
2021
import kotlinx.coroutines.experimental.internal.LockFreeLinkedListNode
2122
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
@@ -26,18 +27,57 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
2627
* Mutex has two states: _locked_ and _unlocked_.
2728
* It is **non-reentrant**, that is invoking [lock] even from the same thread/coroutine that currently holds
2829
* the lock still suspends the invoker.
29-
*
30-
* @param locked initial state of the mutex
3130
*/
32-
public class Mutex(locked: Boolean = false) {
31+
public interface Mutex {
32+
/**
33+
* Factory for [Mutex] instances.
34+
*/
35+
public companion object Factory {
36+
/**
37+
* Creates new [Mutex] instance.
38+
* @param locked initial state of the mutex.
39+
*/
40+
public operator fun invoke(locked: Boolean = false) : Mutex = MutexImpl(locked)
41+
}
42+
43+
/**
44+
* Returns `true` when this mutex is locked.
45+
*/
46+
public val isLocked: Boolean
47+
48+
/**
49+
* Tries to lock this mutex, returning `false` if this mutex is already locked.
50+
*/
51+
public fun tryLock(): Boolean
52+
53+
/**
54+
* Locks this mutex, suspending caller while the mutex is locked.
55+
*
56+
* This suspending function is cancellable. If the [Job] of the current coroutine is completed while this
57+
* function is suspended, this function immediately resumes with [CancellationException].
58+
* Cancellation of suspended lock invocation is *atomic* -- when this function
59+
* throws [CancellationException] it means that the mutex was not locked.
60+
*
61+
* Note, that this function does not check for cancellation when it is not suspended.
62+
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
63+
*/
64+
public suspend fun lock()
65+
66+
/**
67+
* Unlocks this mutex. Throws [IllegalStateException] if invoked on a mutex that is not locked.
68+
*/
69+
public fun unlock()
70+
}
71+
72+
private class MutexImpl(locked: Boolean) : Mutex {
3373
// State is: Empty | UnlockOp | LockFreeLinkedListHead (queue of Waiter objects)
3474
@Volatile
3575
private var state: Any? = if (locked) EmptyLocked else EmptyUnlocked // shared objects while we have no waiters
3676

3777
private companion object {
3878
@JvmStatic
39-
val STATE: AtomicReferenceFieldUpdater<Mutex, Any?> =
40-
AtomicReferenceFieldUpdater.newUpdater(Mutex::class.java, Any::class.java, "state")
79+
val STATE: AtomicReferenceFieldUpdater<MutexImpl, Any?> =
80+
AtomicReferenceFieldUpdater.newUpdater(MutexImpl::class.java, Any::class.java, "state")
4181

4282
@JvmStatic
4383
val EmptyLocked = Empty(true)
@@ -50,15 +90,9 @@ public class Mutex(locked: Boolean = false) {
5090

5191
}
5292

53-
/**
54-
* Returns `true` when this mutex is locked.
55-
*/
56-
public val isLocked: Boolean get() = isLocked(state)
93+
public override val isLocked: Boolean get() = isLocked(state)
5794

58-
/**
59-
* Tries to lock this mutex, returning `false` if this mutex is already locked.
60-
*/
61-
public fun tryLock(): Boolean {
95+
public override fun tryLock(): Boolean {
6296
while (true) { // lock-free loop on state
6397
val state = this.state
6498
when (state) {
@@ -72,18 +106,7 @@ public class Mutex(locked: Boolean = false) {
72106
}
73107
}
74108

75-
/**
76-
* Locks this mutex, suspending caller while the mutex is locked.
77-
*
78-
* This suspending function is cancellable. If the [Job] of the current coroutine is completed while this
79-
* function is suspended, this function immediately resumes with [CancellationException].
80-
* Cancellation of suspended lock invocation is *atomic* -- when this function
81-
* throws [CancellationException] it means that the mutex was not locked.
82-
*
83-
* Note, that this function does not check for cancellation when it is not suspended.
84-
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
85-
*/
86-
public suspend fun lock() {
109+
public override suspend fun lock() {
87110
// fast-path -- try lock
88111
if (tryLock()) return
89112
// slow-path -- suspend
@@ -126,10 +149,7 @@ public class Mutex(locked: Boolean = false) {
126149
}
127150
}
128151

129-
/**
130-
* Unlocks this mutex. Throws [IllegalStateException] if invoked on a mutex that is not locked.
131-
*/
132-
public fun unlock() {
152+
public override fun unlock() {
133153
while (true) { // lock-free loop on state
134154
val state = this.state
135155
when (state) {
@@ -159,7 +179,7 @@ public class Mutex(locked: Boolean = false) {
159179
}
160180

161181
private class Empty(val locked: Boolean) {
162-
override fun toString(): String = "Empty[${if (locked) "Locked" else "Unlocked"}]";
182+
override fun toString(): String = "Empty[${if (locked) "Locked" else "Unlocked"}]"
163183
}
164184

165185
private class Waiter(val cont: CancellableContinuation<Unit>) : LockFreeLinkedListNode()
@@ -175,7 +195,7 @@ public class Mutex(locked: Boolean = false) {
175195
*/
176196
val success = queue.isEmpty
177197
val update: Any = if (success) EmptyUnlocked else queue
178-
STATE.compareAndSet(this@Mutex, this@UnlockOp, update)
198+
STATE.compareAndSet(this@MutexImpl, this@UnlockOp, update)
179199
/*
180200
`helpComplete` invocation from the original `unlock` invocation may be coming too late, when
181201
some other thread had already helped to complete it (either successfully or not).

kotlinx-coroutines-core/src/test/kotlin/guide/example-select-04.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
package guide.select.example04
1919

2020
import kotlinx.coroutines.experimental.*
21-
import kotlinx.coroutines.experimental.selects.select
21+
import kotlinx.coroutines.experimental.channels.*
22+
import kotlinx.coroutines.experimental.selects.*
2223
import java.util.*
2324

2425
fun asyncString(time: Int) = async(CommonPool) {

kotlinx-coroutines-core/src/test/kotlin/guide/example-select-05.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@
1818
package guide.select.example05
1919

2020
import kotlinx.coroutines.experimental.*
21-
import kotlinx.coroutines.experimental.channels.Channel
22-
import kotlinx.coroutines.experimental.channels.ReceiveChannel
23-
import kotlinx.coroutines.experimental.channels.produce
24-
import kotlinx.coroutines.experimental.selects.select
21+
import kotlinx.coroutines.experimental.channels.*
22+
import kotlinx.coroutines.experimental.selects.*
2523

2624
fun switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String>(CommonPool) {
2725
var current = input.receive() // will start with first received deferred value

kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/MutexTest.kt renamed to kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/sync/MutexTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
package kotlinx.coroutines.experimental
17+
package kotlinx.coroutines.experimental.sync
1818

19+
import kotlinx.coroutines.experimental.*
1920
import org.junit.Assert.*
2021
import org.junit.Test
2122

0 commit comments

Comments
 (0)