[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.