Is `StandardTestDispatcher` limited to one thread?...
# coroutines
c
Is
StandardTestDispatcher
limited to one thread? How can I write a test where I need one thread to lock a mutex while another thread waits for the mutex to be unlocked, while the main
runTest
block asserts that the mutex is locked in between?
e
while a coroutine is suspended waiting for a
kotlinx.coroutines.sync.Mutex
(or suspended for any other reason), other coroutines can run on the same thread
c
true, but I need one thread to do something that holds the mutex while the main test thread can assert that it's locked
e
Copy code
assertTrue(mutex.tryLock())
?
c
my problem is I'm not sure how to launch a coroutine in the background that holds the lock for some arbitrary amount of time, but not have that block the test thread from continuing on to assert that the mutex is locked
e
Copy code
@Test
fun test() = runTest {
    val mutex = Mutex()
    val job = launch(start = CoroutineStart.UNDISPATCHED) {
        mutex.withLock {
            delay(100)
        }
    }
    assertFalse(mutex.tryLock())
    job.join()
    assertTrue(mutex.tryLock())
    mutex.unlock()
}
or
Copy code
val job = launch {
        mutex.withLock {
            delay(100)
        }
    }
    delay(1)
    assertFalse(mutex.tryLock())
instead of changing
start
(not guaranteed to work in live code, but in the test scheduler with virtual time, it is fine)
also consider not testing internal implementation details like "which
Mutex
is locked" if possible?
consider https://github.com/Kotlin/kotlinx-lincheck if you do need to test for tricky races
c
Thanks, I'll try these! My next issue is that the code that runs inside of
withLock
is a non-suspending lambda. (In the production code the lambda would be calling a C++ library that may run some computations that take a while.) So I need to find a way for my test function to hold the mutex in a non-suspending context... would
Thread.sleep
be appropriate here? Feels like a code smell still, but I'm not sure of any alternatives
e
no, that will break the test scheduler
if you block the thread, nothing else will run
maybe punt the blocking to another thread so that the test can suspend normally,
Copy code
withContext(Dispatchers.Default) {
    runInterruptible {
        someBlockingJavaCode()
    }
}
but whether that'll work in the test may depend on timeouts, I think
c
also consider not testing internal implementation details like "which
Mutex
is locked" if possible?
The class I'm testing is essentially wrapping this C++ library to ensure only one thread is calling it at a time. So my goal is to test that guarantee
okay cool, I'll try that, haven't heard of
runInterruptible
before
e
there are alternatives, like if you hide the library behind a
Copy code
private val library: Library
private val mutex: Mutex
suspend fun <T> useLibrary(block: (Library) -> T): T = mutex.withLock {
    block(library)
}
then that forces all access outside this code to be serial
then you don't need to test every operation
c
does
runInterruptible
circumvent the need to explicitly make computation code cancellable?
then that forces all access outside this code to be serial
that's essentially what my wrapper is doing
e
runInterruptible
is a bridge between Java thread cancellation and kotlinx.coroutines job cancellation
c
just with additional stuff like
withTimeout
and analytics
e
if your JNI code isn't interruptible it won't actually do anything
but blocking operations (e.g. sleep, IO) in the Java library are nearly all interruptible
(well, the interruptability of local file IO depends on platform, but that aside…)
c
okay thanks, this is really helpful