Coroutines “Asynchronous withTimeout” official tutorial strange behavior

In the official guide “Cancellation and timeouts” section “Asynchronous timeout and resources” there is an example that is supposed to… “If you run the above code you’ll see that it does not always print zero, though it may depend on the timings of your machine you may need to tweak timeouts in this example to actually see non-zero values.”

var acquired = 0

class Resource {
    init { acquired++ } // Acquire the resource
    fun close() { acquired-- } // Release the resource
}

fun main() {
    runBlocking {
        repeat(100_000) { // Launch 100K coroutines
            launch { 
                val resource = withTimeout(60) { // Timeout of 60 ms
                    delay(50) // Delay for 50 ms
                    Resource() // Acquire a resource and return it from withTimeout block     
                }
                resource.close() // Release the resource
            }
        }
    }
    // Outside of runBlocking all coroutines have completed
    println(acquired) // Print the number of resources still acquired
}

I do not understand how that work. If we let the timeout to 60, instances of Resource are never created. We had to go up to 120 to see instances been created. However 60ms looks enough to let include a delay(50) + an instance creation. No?

Does someone could explain that? Thanks in advance.

You are assuming that delay(50) is able to resume after precisely 50 milliseconds. There’s 100,000 coroutines all fighting to resume on the same thread. It may take a bit for these coroutines to all resume.

I modified the example to just show the number of times a Resource was created and reduced the repeat count to 10_000. When I ran it on Kotlin playground, it showed that no Resource had been created. So 10ms was not nearly enough time in this case.

When I reduced the count repeat count to 100, then it created 100 Resource instances.

1 Like

Thanks @nickallendev.
You put the finder exactly on the killing point : 100_000 coroutines fighting.

So If we take back the example to more realistic conditions eg: 10, it works perfectly.

1 Like

Remember that the point of the example is that it does not always “work perfectly”. There’s a race condition where the internal timeout code could set withTimeout's result to an exception any time after delay(50) resuming and before the “successful path” code sets withTimeout's result to a successful result. If the result is set to an exception first, the “successful” result of the lambda is ignored.

Ok thank you @nickallendev