[coroutines] Can channel lose messages if receive() was cancelled?

I have an actor that calls receive() inside withTimeoutOrNull block. And sometimes messages that was sent into the channel is lost. This happens after timeout was raised.

Channel.receive documentation says:

Cancellation of suspended receive is atomic – when this function throws CancellationException it means that the element was not retrieved from this channel.

So I think receive() should not lose messages.
Or there is a bug in my code.

There is an example project:

And when I run ./gradlew jcstress I got a lot of errors like next:

[FAILED] channel.ChannelWithTimeoutStressTest                                                                                                      
  (fork: #1, iteration #1, JVM args: [-XX:+UnlockDiagnosticVMOptions, -XX:+WhiteBoxAPI, -XX:-RestrictContended, -Dfile.encoding=UTF-8, -Duser.country=UA, -Duser.language=en, -Duser.variant, -XX:TieredStopAtLevel=1])
Observed state   Occurrences   Expectation  Interpretation                                              
             0            39    ACCEPTABLE  OK                                                          
             1             1     FORBIDDEN  Message was lost

withTimeoutOrNull that you are using does not have the property of atomic cancellation, hence lost messages.

Think such a behaviour is unclear. It should be properly indicated in the documentation. At the moment withTimeout and withTimeoutOrNull have almost identical description from which it may seem that withTimeoutOrNull is just a wrapper. It may be misleading.

withTimeout is not atomic either. Currently, only suspending functions that have explicit mention of “atomic cancellation” in their documentation provide this property of atomic cancellation.

I’ve just tried to use select with onTimeout clause and it works fine.

IMHO it is really counterintuitive that withTimeout throws away result of a single operation which is atomically cancellable, making next code is broken:

withTimeout(1) {
    channel.receive()
}

Thanks for the help.