diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt b/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt index e66355588..6433ca44e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt @@ -1,6 +1,8 @@ package org.koitharu.kotatsu.utils import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.isActive import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -32,11 +34,13 @@ class CompositeMutex : Set { } suspend fun lock(element: T) { - waitForRemoval(element) - mutex.withLock { - val lastValue = data.put(element, LinkedList>()) - check(lastValue == null) { - "CompositeMutex is double-locked for $element" + while (currentCoroutineContext().isActive) { + waitForRemoval(element) + mutex.withLock { + if (data[element] == null) { + data[element] = LinkedList>() + return + } } } } diff --git a/app/src/test/java/org/koitharu/kotatsu/utils/CompositeMutexTest.kt b/app/src/test/java/org/koitharu/kotatsu/utils/CompositeMutexTest.kt new file mode 100644 index 000000000..d6f1e87ef --- /dev/null +++ b/app/src/test/java/org/koitharu/kotatsu/utils/CompositeMutexTest.kt @@ -0,0 +1,39 @@ +package org.koitharu.kotatsu.utils + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.coroutines.yield +import org.junit.Assert.assertNull +import org.junit.Test + +class CompositeMutexTest { + + @Test + fun testSingleLock() = runTest { + val mutex = CompositeMutex() + mutex.lock(1) + mutex.lock(2) + mutex.unlock(1) + assert(mutex.size == 1) + mutex.unlock(2) + assert(mutex.isEmpty()) + } + + @Test + fun testDoubleLock() = runTest { + val mutex = CompositeMutex() + repeat(2) { + launch(Dispatchers.Default) { + mutex.lock(1) + } + } + yield() + mutex.unlock(1) + val tryLock = withTimeoutOrNull(1000) { + mutex.lock(1) + } + assertNull(tryLock) + } +} \ No newline at end of file