Skip to content

Commit a3763e8

Browse files
committed
Improve Semaphore API
* Improved documentation * Detailed error messages
1 parent 96c5a49 commit a3763e8

File tree

1 file changed

+19
-20
lines changed

1 file changed

+19
-20
lines changed

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

+19-20
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
package kotlinx.coroutines.sync
22

3-
import kotlinx.atomicfu.atomic
4-
import kotlinx.atomicfu.atomicArrayOfNulls
5-
import kotlinx.atomicfu.getAndUpdate
6-
import kotlinx.atomicfu.loop
3+
import kotlinx.atomicfu.*
74
import kotlinx.coroutines.*
85
import kotlinx.coroutines.internal.*
9-
import kotlin.coroutines.resume
10-
import kotlin.math.max
6+
import kotlin.coroutines.*
7+
import kotlin.jvm.*
8+
import kotlin.math.*
119

1210
/**
13-
* A counting semaphore for coroutines. It maintains a number of available permits.
14-
* Each [acquire] suspends if necessary until a permit is available, and then takes it.
11+
* A counting semaphore for coroutines that logically maintains a number of available permits.
12+
* Each [acquire] takes a single permit or suspends until it is available.
1513
* Each [release] adds a permit, potentially releasing a suspended acquirer.
14+
* Semaphore is fair and maintains a FIFO order of acquirers.
1615
*
16+
* Semaphores are mostly used to limit the number of coroutines that have an access to particular resource.
1717
* Semaphore with `permits = 1` is essentially a [Mutex].
1818
**/
1919
public interface Semaphore {
@@ -29,11 +29,12 @@ public interface Semaphore {
2929
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
3030
* function is suspended, this function immediately resumes with [CancellationException].
3131
*
32-
* *Cancellation of suspended semaphore acquisition` is atomic* -- when this function
32+
* *Cancellation of suspended semaphore acquisition is atomic* -- when this function
3333
* throws [CancellationException] it means that the semaphore was not acquired.
3434
*
35-
* Note, that this function does not check for cancellation when it is not suspended.
36-
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
35+
* Note, that this function does not check for cancellation when it does not suspend.
36+
* Use [CoroutineScope.isActive] or [CoroutineScope.ensureActive] to periodically
37+
* check for cancellation in tight loops if needed.
3738
*
3839
* Use [tryAcquire] to try acquire a permit of this semaphore without suspension.
3940
*/
@@ -49,8 +50,7 @@ public interface Semaphore {
4950
/**
5051
* Releases a permit, returning it into this semaphore. Resumes the first
5152
* suspending acquirer if there is one at the point of invocation.
52-
* Throws [IllegalStateException] if there is no acquired permit
53-
* at the point of invocation.
53+
* Throws [IllegalStateException] if the number of [release] invocations is greater than the number of preceding [acquire].
5454
*/
5555
public fun release()
5656
}
@@ -83,8 +83,8 @@ private class SemaphoreImpl(
8383
private val permits: Int, acquiredPermits: Int
8484
) : Semaphore, SegmentQueue<SemaphoreSegment>() {
8585
init {
86-
require(permits > 0) { "Semaphore should have at least 1 permit" }
87-
require(acquiredPermits in 0..permits) { "The number of acquired permits should be in 0..permits" }
86+
require(permits > 0) { "Semaphore should have at least 1 permit, but had $permits" }
87+
require(acquiredPermits in 0..permits) { "The number of acquired permits should be in 0..$permits" }
8888
}
8989

9090
override fun newSegment(id: Long, prev: SemaphoreSegment?) = SemaphoreSegment(id, prev)
@@ -126,8 +126,8 @@ private class SemaphoreImpl(
126126
resumeNextFromQueue()
127127
}
128128

129-
internal fun incPermits() = _availablePermits.getAndUpdate { cur ->
130-
check(cur < permits) { "The number of acquired permits cannot be greater than `permits`" }
129+
fun incPermits() = _availablePermits.getAndUpdate { cur ->
130+
check(cur < permits) { "The number of released permits cannot be greater than $permits" }
131131
cur + 1
132132
}
133133

@@ -176,6 +176,8 @@ private class CancelSemaphoreAcquisitionHandler(
176176

177177
private class SemaphoreSegment(id: Long, prev: SemaphoreSegment?): Segment<SemaphoreSegment>(id, prev) {
178178
val acquirers = atomicArrayOfNulls<Any?>(SEGMENT_SIZE)
179+
private val cancelledSlots = atomic(0)
180+
override val removed get() = cancelledSlots.value == SEGMENT_SIZE
179181

180182
@Suppress("NOTHING_TO_INLINE")
181183
inline fun get(index: Int): Any? = acquirers[index].value
@@ -186,9 +188,6 @@ private class SemaphoreSegment(id: Long, prev: SemaphoreSegment?): Segment<Semap
186188
@Suppress("NOTHING_TO_INLINE")
187189
inline fun getAndSet(index: Int, value: Any?) = acquirers[index].getAndSet(value)
188190

189-
private val cancelledSlots = atomic(0)
190-
override val removed get() = cancelledSlots.value == SEGMENT_SIZE
191-
192191
// Cleans the acquirer slot located by the specified index
193192
// and removes this segment physically if all slots are cleaned.
194193
fun cancel(index: Int): Boolean {

0 commit comments

Comments
 (0)