From 511983ec5a3107a540a6984504556b5a002f71d6 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 11 Jul 2024 14:33:46 +0200 Subject: [PATCH] Forbid casting Mutex to a Semaphore Fixes #4026 --- kotlinx-coroutines-core/common/src/sync/Mutex.kt | 2 +- .../common/src/sync/Semaphore.kt | 16 ++++++++++------ .../common/test/sync/MutexTest.kt | 5 +++++ .../jvm/test/lincheck/SemaphoreLincheckTest.kt | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt index fd5e921828..093c367970 100644 --- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -127,7 +127,7 @@ public suspend inline fun Mutex.withLock(owner: Any? = null, action: () -> T } -internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 else 0), Mutex { +internal open class MutexImpl(locked: Boolean) : SemaphoreAndMutexImpl(1, if (locked) 1 else 0), Mutex { /** * After the lock is acquired, the corresponding owner is stored in this field. * The [unlock] operation checks the owner and either re-sets it to [NO_OWNER], diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt index c1bd24990e..7cc13f81f8 100644 --- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt +++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt @@ -87,7 +87,7 @@ public suspend inline fun Semaphore.withPermit(action: () -> T): T { } @Suppress("UNCHECKED_CAST") -internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Semaphore { +internal open class SemaphoreAndMutexImpl(private val permits: Int, acquiredPermits: Int) { /* The queue of waiting acquirers is essentially an infinite array based on the list of segments (see `SemaphoreSegment`); each segment contains a fixed number of slots. To determine a slot for each enqueue @@ -144,11 +144,11 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int * cannot be greater than 2^31 in any real application. */ private val _availablePermits = atomic(permits - acquiredPermits) - override val availablePermits: Int get() = max(_availablePermits.value, 0) + val availablePermits: Int get() = max(_availablePermits.value, 0) private val onCancellationRelease = { _: Throwable, _: Unit, _: CoroutineContext -> release() } - override fun tryAcquire(): Boolean { + fun tryAcquire(): Boolean { while (true) { // Get the current number of available permits. val p = _availablePermits.value @@ -167,7 +167,7 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int } } - override suspend fun acquire() { + suspend fun acquire() { // Decrement the number of available permits. val p = decPermits() // Is the permit acquired? @@ -239,7 +239,7 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int } } - override fun release() { + fun release() { while (true) { // Increment the number of available permits. val p = _availablePermits.getAndIncrement() @@ -346,12 +346,16 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int } else false } is SelectInstance<*> -> { - trySelect(this@SemaphoreImpl, Unit) + trySelect(this@SemaphoreAndMutexImpl, Unit) } else -> error("unexpected: $this") } } +private class SemaphoreImpl( + permits: Int, acquiredPermits: Int +): SemaphoreAndMutexImpl(permits, acquiredPermits), Semaphore + private fun createSegment(id: Long, prev: SemaphoreSegment?) = SemaphoreSegment(id, prev, 0) private class SemaphoreSegment(id: Long, prev: SemaphoreSegment?, pointers: Int) : Segment(id, prev, pointers) { diff --git a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt index 7c6b8ab6c1..c2d555f32b 100644 --- a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt +++ b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt @@ -192,4 +192,9 @@ class MutexTest : TestBase() { } } } + + @Test + fun testMutexIsNotSemaphore() { + assertIsNot(Mutex()) + } } diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt index 12b44d57fa..e99e8a185c 100644 --- a/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt +++ b/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt @@ -9,7 +9,7 @@ import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.* abstract class SemaphoreLincheckTestBase(permits: Int) : AbstractLincheckTest() { - private val semaphore = SemaphoreImpl(permits = permits, acquiredPermits = 0) + private val semaphore = SemaphoreAndMutexImpl(permits = permits, acquiredPermits = 0) @Operation fun tryAcquire() = semaphore.tryAcquire()