Context details
The Context
carries a deadline, a cancellation signal, Done
and other values
across processes and APIs. It is defined as an interface:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Note: these methods may be called by multiple goroutines simultaneously.
The package provides different types of contexts, with different service levels:
- emtpyCtx: it's one that is never cancelled, has no values and no deadline
- cancelCtx: is one that can be cancelled. When cancelled, it also cancels all its children that implement cancel.
- valueCtx: carries a key-value pair. Delegates all other calls (such as cancel) to its embedded context.
- timerCtx: carries a timer and a deadline. It embeds
cancelCtx
to implementDone
andErr
.cancel
is implementing by stopping its timer and delegating the call tocancelCtx
It's important to note that as a user of the package, we only have access to these types through
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
CancelFunc
type CancelFunc func()
When creating a context, there are some factory functions. And all of them return a CancelFunc
type.
This is, as its name indicates, a function. It tells an operation to abandon its work. It's async, and
it can be called by multiple goroutines simultaneously. After the first call, the subsequent calls are
no-ops.
As an example, for the cancelCtx
, cancel
method exists. It closes the done
channel and cancels
all of its children, plus some other cleaning tasks.
Examples
This is an extension of the original example provided in the std library. It depicts the usage of context within several goroutines that form a tree.
Once the main goroutine triggers the cancel
function, all the others
are also cancelled.
package main
import (
"context"
"fmt"
"time"
)
func main() {
// gen generates integers in a separate goroutine and
// sends them to the returned channel.
// The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak
// the internal goroutine started by gen.
gen := func(ctx context.Context) <-chan int {
ctx2, cancel := context.WithCancel(ctx)
defer cancel()
// propagating the cancel context to a new goroutine
go doStuff(ctx2)
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("Closing gen")
fmt.Println(n)
return // returning not to leak the goroutine
case dst <- n:
n++
}
}
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
// defer cancel() // cancel when we are finished consuming integers
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
cancel()
for {
time.Sleep(3 * time.Second)
break
}
}
func doStuff(ctx context.Context) {
fmt.Println("Starting child")
for {
select {
case <-ctx.Done():
fmt.Println("Closing child")
return
}
}
}
A plausible output (it's non-deterministic):
1
2
3
4
5
Closing gen
6
Starting child
Closing child
Using WithTimeout
This example is rather simple. But it shows how to propagate a timeout into a tree.
Here we show how the main goroutine receives a Done
signal after 50 miliseconds. This
triggers (through cancel
) subsequent Done
signals to all the goroutines that
where created with the WithTimeout
method.
package main
import (
"context"
"fmt"
"time"
)
func main() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
ctx2, cancel2 := context.WithTimeout(context.Background(), 50*time.Millisecond)
func(ctx2 context.Context) {
select {
case <-ctx2.Done():
fmt.Println(ctx2.Err()) // prints "context deadline exceeded"
return
case <-time.After(100 * time.Millisecond):
fmt.Println("overslept 2")
}
}(ctx2)
defer cancel()
defer cancel2()
select {
case <-time.After(100 * time.Millisecond):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
}
Context being used in the real world
These are some libraries making heavy use of this pattern: