JetBrains / lincheck

Framework for testing concurrent data structures
Mozilla Public License 2.0
572 stars 33 forks source link

coroutine finishing early #121

Closed btwilk closed 1 year ago

btwilk commented 1 year ago

Version: 2.15 I'm model checking some code that uses Mutex. I ran into a case where lincheck incorrectly considers a coroutine that should still be running to have finished. Here is a minimal example:

class CoroutineLinTest {

    private var x = AtomicInteger()
    private val mutex = Mutex()

    @Operation(cancellableOnSuspension = false, allowExtraSuspension = true)
    suspend fun getAndInc(): Int {
        while (true) {
            val snapshot = x.get()
            mutex.withLock {
                if (snapshot == x.get()) {
                    return x.getAndIncrement()
                }
            }
        }
    }

    @Test
    fun test() = ModelCheckingOptions().check(this::class)
}

Here is the assertion error: coroutine-assertion-error.txt

In contrast, the equivalent code with thread synchronization model checks as expected (no counterexamples found):

class ThreadLinTest {

    private var x = AtomicInteger()
    private val mutex = Object()

    @Operation
    fun getAndInc(): Int {
        while (true) {
            val snapshot = x.get()
            synchronized(mutex) {
                if (snapshot == x.get()) {
                    return x.getAndIncrement()
                }
            }
        }
    }

    @Test
    fun test() = ModelCheckingOptions().check(this::class)
}
ndkoval commented 1 year ago

@btwilk Unfortunetely, we do not support multiple suspensions at the moment, but we are considering imrpoving this part later.

alefedor commented 1 year ago

@btwilk Suprisingly, we did not have applications with multiple suspension points per operation. Coroutine-based data structures (like queues or channels) mostly have only one natural suspension point. I opened a more general issue, so I am closing this one