Properly use acquired permits in Semaphore
Fixes #1423
diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
index 160dcac..6ab377d 100644
--- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
+++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
@@ -96,7 +96,7 @@
* and the maximum number of waiting acquirers cannot be greater than 2^31 in any
* real application.
*/
- private val _availablePermits = atomic(permits)
+ private val _availablePermits = atomic(permits - acquiredPermits)
override val availablePermits: Int get() = max(_availablePermits.value, 0)
// The queue of waiting acquirers is essentially an infinite array based on `SegmentQueue`;
diff --git a/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt b/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt
index dc14a12..b4ff88b 100644
--- a/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt
+++ b/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt
@@ -1,9 +1,6 @@
package kotlinx.coroutines.sync
-import kotlinx.coroutines.TestBase
-import kotlinx.coroutines.cancelAndJoin
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
@@ -140,4 +137,35 @@
job1.cancel()
finish(6)
}
+
+ @Test
+ fun testAcquiredPermits() = runTest {
+ val semaphore = Semaphore(5, acquiredPermits = 4)
+ assertEquals(semaphore.availablePermits, 1)
+ semaphore.acquire()
+ assertEquals(semaphore.availablePermits, 0)
+ assertFalse(semaphore.tryAcquire())
+ semaphore.release()
+ assertEquals(semaphore.availablePermits, 1)
+ assertTrue(semaphore.tryAcquire())
+ }
+
+ @Test
+ fun testReleaseAcquiredPermits() = runTest {
+ val semaphore = Semaphore(5, acquiredPermits = 4)
+ assertEquals(semaphore.availablePermits, 1)
+ repeat(4) { semaphore.release() }
+ assertEquals(5, semaphore.availablePermits)
+ assertFailsWith<IllegalStateException> { semaphore.release() }
+ repeat(5) { assertTrue(semaphore.tryAcquire()) }
+ assertFalse(semaphore.tryAcquire())
+ }
+
+ @Test
+ fun testIllegalArguments() {
+ assertFailsWith<IllegalArgumentException> { Semaphore(-1, 0) }
+ assertFailsWith<IllegalArgumentException> { Semaphore(0, 0) }
+ assertFailsWith<IllegalArgumentException> { Semaphore(1, -1) }
+ assertFailsWith<IllegalArgumentException> { Semaphore(1, 2) }
+ }
}
\ No newline at end of file