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?
Related
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?
incoming := make(chan int)
done := make(chan struct{})
...
close(done)
...
select {
case incoming <- order:
// logic
case <-done:
return
}
Let's say you close the done channel before the select statement:
1.The case <-done will be selected.
2.Is this because when close(done) executes, the done channel will be in a state, in which the select statement will consider the "case <-done" a match over the writing case "incoming <- order"?
The select statement blocks until one or more of the communication operations in its cases can proceed. As soon as one or more communication operations can proceed select will randomly choose one of them.
A receive operation on a closed channel can proceed immediately.
Select Statement:
Execution of a "select" statement proceeds in several steps:
For all the cases in the statement, the channel operands of
receive operations and the channel and right-hand-side expressions
of send statements are evaluated exactly once, in source order,
upon entering the "select" statement. The result is a set of
channels to receive from or send to, and the corresponding values
to send. Any side effects in that evaluation will occur
irrespective of which (if any) communication operation is selected
to proceed. Expressions on the left-hand side of a RecvStmt with a > short variable declaration or assignment are not yet evaluated.
If one or more of the communications can proceed, a single one
that can proceed is chosen via a uniform pseudo-random selection.
Otherwise, if there is a default case, that case is chosen. If
there is no default case, the "select" statement blocks until at
least one of the communications can proceed.
Unless the selected case is the default case, the respective
communication operation is executed.
If the selected case is a RecvStmt with a short variable
declaration or an assignment, the left-hand side expressions are
evaluated and the received value (or values) are assigned.
The statement list of the selected case is executed.
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.
I'm writing some code where I'm passing data from one channel to another one. Following some intuition and this answer I expected the following code to work (other is a sufficiently big buffered channel and out is the source channel):
for {
select {
case other <- (<-out):
log.Warn("C")
}
}
And it does! But the other cases don't trigger at all, e.g. there are no Ds in the logs for the code below:
for {
select {
case other <- (<-out):
log.Warn("C")
default:
log.Warn("D")
}
}
Using the more traditional solution, there are Ds all over the logs:
for {
select {
case msg := <-out:
other <- msg
log.Warn("C")
default:
log.Warn("D")
}
}
Obviously, I'm going with the usual solution, but I still don't know why the unusual one does not work as expected.
I suspect the answer lies somewhere in The Go Memory Model but I can't quite figure out what is exactly happening in this case.
I have put together some playgrounds where you can check out this behavior:
Unusal (there are no Ds at all)
Usual (there are a lot of Ds, you might have to try it locally to see anything other than Ds)
Thanks in advance to anyone who can shed some light on this!
When you have this:
ch := make(chan int, 10)
// ...
select {
case ch <- <-out:
fmt.Println("C")
default:
fmt.Println("D")
}
The communication op of the first case is ch <- something, where something is <-out. But the something is evaluated first, and only then is checked which communication op of the cases can proceed.
So <-out will block as long as it needs, and then ch <- something is checked if it can proceed. Since you used a big enough buffer, it can always proceed in your example, so default is never chosen.
Spec: Select statements:
Execution of a "select" statement proceeds in several steps:
For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send. Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed. Expressions on the left-hand side of a RecvStmt with a short variable declaration or assignment are not yet evaluated.
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
Unless the selected case is the default case, the respective communication operation is executed.
If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are evaluated and the received value (or values) are assigned.
The statement list of the selected case is executed.
If you lower the buffer of ch, you will see occasional Ds printed in the output (try it on the Go Playground).
ch := make(chan int, 2)
Let's say I have a bunch of goroutines running that do a HTTP request and will return at a random time. I start the goroutines (with waitgroups and one cancel among them) and I want them to keep executing the request until it returns an OK (may time out). When the first one returns, it calls the cancel and every other goroutine should just stop.
I'm not sure how I can set up the goroutine function case that will execute the code if context is not done?
As an example, I know how to use time.After() but how do I make it such that it executes the code immediately instead of after a duration?
for {
select {
case <-ctx.Done():
wg.Done()
return
case <-time.After(time.Second):
// code goes here
}
}
What can I replace the time case with?
If I understand you correctly, you have multiple goroutines racing to compute the result to the same request. I believe you're looking for something like this:
for {
select {
case <-ctx.Done():
wg.Done()
return
default:
}
// Do stuff
}
The select will immediately drop to default if context is not canceled. However, context may still cancel while you're running your code, so you may end up with multiple goroutines trying to write the result, so you have to synchronize that as well. You can write your result to a channel, and when received, you can cancel the context. However you may still have goroutines waiting to write to that channel, so you have to keep reading from the channel until all goroutines terminate.
I have an application with multiple goroutines that are running for loops, and need a way to signal these for loops to break, and to test whether the timeout case occurred. I was looking into using a shared channel with select statements to accomplish this as follows:
// elsewhere in the code, this channel is created, and passed below
done := make(chan struct{})
time.AfterFunc(timeout, func() { close(done) })
...
go func() {
Loop:
for {
select {
case <-done:
break Loop
default:
foo()
time.Sleep(1 * time.Second)
}
}
select {
case <-done:
panic("timed out!")
default:
// ok
}
}()
Is this a valid way to accomplish this? What I'm most concerned about is that the branch of a select that is chosen could be non-deterministic, so that default may be chosen even if one of the cases is ready. Is this possible? Is there any documentation that states that a matching case is guaranteed to have preference over a default. The concern is that the for loop above could loop several times after done is closed and/or report success even though a timeout occurred.
The Go Programming Language Specification
Select statements
Execution of a "select" statement proceeds in several steps:
For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send
statements are evaluated exactly once, in source order, upon entering
the "select" statement. The result is a set of channels to receive
from or send to, and the corresponding values to send. Any side
effects in that evaluation will occur irrespective of which (if any)
communication operation is selected to proceed. Expressions on the
left-hand side of a RecvStmt with a short variable declaration or
assignment are not yet evaluated.
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.
Otherwise, if there is a default case, that case is chosen. If there
is no default case, the "select" statement blocks until at least one
of the communications can proceed.
Unless the selected case is the default case, the respective communication operation is executed.
If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are
evaluated and the received value (or values) are assigned.
The statement list of the selected case is executed.
"What I'm most concerned about is that the branch of a select that is
chosen could be non-deterministic, so that default may be chosen even
if one of the cases is ready. Is this possible?"
No. See step 2 of the select specification.