Andrey Tabakov
08/26/2025, 11:39 AM2025-08-25 22:44:15.278 [DefaultDispatcher-worker-10] WARN a.r.backend.api.KtorApp - null
java.lang.IllegalMonitorStateException: null
at java.base/java.util.concurrent.locks.ReentrantReadWriteLock$Sync.tryRelease(Unknown Source)
at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
at java.base/java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock.unlock(Unknown Source)
at ai.retable.backend.dataflow.processing.design.App.veryImportant(App.kt:619)
Code that cause error:
val lock = ReentrantReadWriteLock() // java.util.concurrent.locks
suspend fun veryImportant() {
lock.write {
withTimeout(TIMEOUT) {
// do some suspend work
}
}
}
Java code that cause an error:
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
So, does it mean I can't use java.util.concurrent.locks.ReentrantReadWriteLock
with Kotlin coroutines because the thread can change?ross_a
08/26/2025, 11:46 AM.write
suspends, and the coroutine dispatcher has more than one thread, then yes the thread can change
There's nothing really coroutine related in the code above though, no scope, no suspend, no evidence that coroutines are used at all?ross_a
08/26/2025, 11:47 AMAndrey Tabakov
08/26/2025, 11:47 AMlock.write{
withTimeout(TIMEOUT) {
// do some suspend work
}
}
Andrey Tabakov
08/26/2025, 11:48 AMross_a
08/26/2025, 11:50 AMAndrey Tabakov
08/26/2025, 11:51 AMross_a
08/26/2025, 11:51 AM/** A non-reentrant suspending implementation of a Read-Write lock. */
class ReadWriteLock {
private var readerCount = 0
private val readerMutex = Mutex()
private val writerMutex = Mutex()
suspend fun <R> withWriteLock(block: suspend () -> R): R = writerMutex.withLock(null) { block() }
suspend fun <R> withReadLock(block: suspend () -> R): R =
withContext(NonCancellable) {
readLock()
try {
block()
} finally {
readUnlock()
}
}
suspend fun readLock() =
withContext(NonCancellable) {
readerMutex.withLock {
readerCount++
if (readerCount == 1) writeLock()
}
}
suspend fun readUnlock() =
withContext(NonCancellable) {
readerMutex.withLock {
readerCount--
if (readerCount == 0) writeUnlock()
}
}
suspend fun writeLock() = writerMutex.lock()
fun writeUnlock() = writerMutex.unlock()
}
ross_a
08/26/2025, 11:52 AMAndrey Tabakov
08/26/2025, 11:53 AMAndrey Tabakov
08/26/2025, 11:53 AM/**
* Executes the given [action] under the write lock of this lock.
*
* The function does upgrade from read to write lock if needed, but this upgrade is not atomic
* as such upgrade is not supported by [ReentrantReadWriteLock].
* In order to do such upgrade this function first releases all read locks held by this thread,
* then acquires write lock, and after releasing it acquires read locks back again.
*
* Therefore if the [action] inside write lock has been initiated by checking some condition,
* the condition must be rechecked inside the [action] to avoid possible races.
*
* @return the return value of the action.
*/
@kotlin.internal.InlineOnly
public inline fun <T> ReentrantReadWriteLock.write(action: () -> T): T {
contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
val rl = readLock()
val readCount = if (writeHoldCount == 0) readHoldCount else 0
repeat(readCount) { rl.unlock() }
val wl = writeLock()
wl.lock()
try {
return action()
} finally {
repeat(readCount) { rl.lock() }
wl.unlock()
}
}
package kotlin.concurrent.Locks.kt
ross_a
08/26/2025, 11:57 AMsuspend
code is no longer really suspending, as you are blocking the coroutine thread with the lockAndrey Tabakov
08/26/2025, 11:59 AMross_a
08/26/2025, 12:02 PMAndrey Tabakov
08/26/2025, 12:06 PMross_a
08/26/2025, 12:08 PMsuspend
it is essentially registering a callback and can resume on a different thread to the one it started on so best to think of it in that regardAndrey Tabakov
08/26/2025, 12:13 PMAndrey Tabakov
08/26/2025, 12:25 PMReadWriteLock
because multiple simultaneous reads are allowed, but only one write can occur at a time. Using a mutex on the readLock achieves nothing, isn't it?ross_a
08/26/2025, 12:40 PMAndrey Tabakov
08/26/2025, 12:43 PMAndrey Tabakov
08/26/2025, 12:43 PMkevin.cianfarini
08/26/2025, 1:15 PMkevin.cianfarini
08/26/2025, 1:16 PMross_a
08/26/2025, 2:53 PMkevin.cianfarini
08/26/2025, 2:54 PMkevin.cianfarini
08/26/2025, 2:55 PMNonCancellable
usages in the above, too.ross_a
08/26/2025, 2:56 PMkevin.cianfarini
08/26/2025, 2:57 PM