I don't understand how the Done() channel in context.Context can work as intended. The module documentation (and source code using it) relies on this pattern:
select {
case <-ctx.Done():
return ctx.Err()
case results <- result:
}
The channel return by Done() is closed if the Context is canceled or timed out, and the Err() variable holds the reason.
I have two questions regarding this approach:
What's the behavior of select when a channel is closed? When and why is the case entered? Does the fact that there's no assignment have relevance?
According to the language reference:
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.
If the choice is random, how does that pattern guarantee that I won't be sending results down a pipeline when the Context is canceled? I would understand if the cases were evaluated in declaration order (and closed channel cases were selected).
If I'm completely off the track here, please explain this to me from a better angle.
This case:
case <-ctx.Done():
Has the communication op:
<-ctx.Done()
It's a receive from a channel. Spec: Receive operator:
A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.
So when the channel returned by ctx.Done() is closed, a receive from it can proceed immediately. Thus control flow can enter into this case.
If the other case (results <- result) can also proceed when the context is cancelled, one is chosen (pseudo-)randomly, there is no guarantee which one it will be.
If you don't want to send a value on results if the context is already cancelled, check the ctx.Done() channel before the select with another non-blocking select:
select {
case <-ctx.Done():
return ctx.Err()
default:
}
select {
case <-ctx.Done():
return ctx.Err()
case results <- result:
}
Note that you must add a default branch to the first select, else it would block until the context is cancelled. If there is a default branch and the context is not yet cancelled, default branch is chosen and so the control flow can go to the second select.
See related questions:
Force priority of go select statement
How does select work when multiple channels are involved?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With