context: the docs should better clarify the context timeout #70945
Description
What is the URL of the page with the issue?
https://pkg.go.dev/[email protected]
What is your user agent?
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36
Screenshot
No response
What did you do?
Inspired by context package clarification made in #68923.
Since the context
is one of the core package for golang concurrent programming, I believe that every segment of its behavior should be clearly written and documented.
In many places, the context
package documentation makes a "difference" (as it should IMO) between context cancellation (done by calling the cancel
function) and context timeout (deadline passed). For example:
- two error variables and their descriptions:
- Canceled is the error returned by [Context.Err] when the context is canceled.
- DeadlineExceeded is the error returned by [Context.Err] when the context's deadline passes.
- the description of
AfterFunc
uses the term "done" to denote both situations:
AfterFunc arranges to call f in its own goroutine after ctx is done (canceled or timed out).
- the description of
Err() error
also makes difference
// If Done is closed, Err returns a non-nil error explaining why:
// Canceled if the context was canceled
// or DeadlineExceeded if the context's deadline passed.
etc...
However, in some places, the documentation remains unclear about the time-out.
For example:
- function
Cause
:
Cause returns a non-nil error explaining why c was canceled. The first cancellation of c or one of its parents sets the cause. If that cancellation happened via a call to CancelCauseFunc(err), then Cause returns err. Otherwise Cause(c) returns the same value as c.Err(). Cause returns nil if c has not been canceled yet.
From the last sentence to the first.
It says that Cause
returns nil if c has not yet been canceled. It is not true. It returns a non-nil value when the context has not yet been canceled (the cancel function has not been called) but deadline has expired (its or one of its parents). IMO the same term used by AfterFunc
- done - can be used here as well. Thus, "Cause returns nil if c is not done yet (canceled or timed out; itself or one of its parent)".
Next three sentences explain only the behavior when the context is cancelled, but not when the c's or one its parents deadline has passed. It may seem that it is covered under "Otherwise, ..." but to me it looks more like it covers the case when CancelFunc
is called instead of CancelCauseFunc
.
The first sentence is incomplete. Cause
returns a non-nil error not only when c is canceled but also when c or any of its parent contexts times out. Again, IMO the term done used in the AfterFunc
fits well here. Thus, "Cause returns a non-nil error explaining why c is done (canceled or timed out; itself or one of its parent)."
- type
CancelCauseFunc
:
If the context has already been canceled, CancelCauseFunc does not set the cause.
This seems incomplete to me. It also does not set the cause in the case when the deadline of the context or one of its parents has passed. IMO the term done can be used here again. Further, the examples should also be corrected.
- function
AfterFunc
:
AfterFunc arranges to call f in its own goroutine after ctx is done (canceled or timed out). If ctx is already done, AfterFunc calls f immediately in its own goroutine.
Everything looks great here, but I believe it should be mentioned that this also applies when one of the parent contexts times out. Nowhere in the specification does it state that if a context times out, all its derived children also time out, which makes the description of AfterFunc
feel somewhat incomplete. IMO, we just need to add the note about parents that I mentioned earlier for done in the other examples.
- Overview:
There is only a note that if a Context is canceled, all contexts derived from it are also canceled.
When a Context is canceled, all Contexts derived from it are also canceled.
IMO, it should also be noted that if a Context times out, all Contexts derived from it also time out (even if they don't have their own timer, such as those created with WithCancel
). For instance, in the following example ctxChild (even though created without timer) will timeout together with its parent:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctxParent, _ := context.WithDeadline(context.TODO(), time.Now().Add(time.Millisecond))
ctxChild, _ := context.WithCancel(ctxParent) // context without "timer"
time.Sleep(time.Second)
ch := make(chan struct{})
context.AfterFunc(ctxChild, func() {
fmt.Println("hi")
ch <- struct{}{}
})
<-ch
}
In this way, there would be no need to add notes about parents within the term done, thus it will be as it currently appears in AfterFunc
.
What did you see happen?
Insufficiently explained behavior in the case of a timeout.
What did you expect to see?
Adding a note in the overview about the relationship between a context and its child in the case of a timeout, and the usage of the term done.