The context package in Go
The context package is used to carry deadlines, cancellation signals, and request-scoped values across processes. It is mostly used to enable cancellation of processes once they have started.
A context can be created in several ways, and can be cancelled automatically or manually using a cancel function. Whatever the case, a deferred call to cancel should exist, as it will free up resources related to that context once it is not used.
Example carrying a value:
To avoid collisions between packages using context, a custom type definition should be used for the key.
type s string
const key s = "foo"
func main() {
ctx := context.WithValue(context.Background(), key, "bar")
value := ctx.Value(key)
fmt.Println(value)
}
// Output:
// bar
By default, the type of the value will be any
. We can cast it to another type for better usage:
type s string
const key s = "foo"
func main() {
ctx := context.WithValue(context.Background(), key, "bar")
value, ok := ctx.Value(key).(string)
if !ok {
fmt.Println("not found")
return
}
fmt.Println(value)
}
// Output:
// bar
Example with a timeout:
const (
max = 5
timeout = 3 * time.Second
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
result := make(chan string)
go func() {
fmt.Println("working on it...")
result <- slowOperation()
}()
select {
case r := <-result:
fmt.Println(r)
case <-ctx.Done():
fmt.Println("operation timed out")
}
}
func slowOperation() string {
rand.Seed(time.Now().Unix())
duration := rand.Intn(max)
time.Sleep(time.Duration(duration) * time.Second)
return fmt.Sprintf("operation completed in %v seconds", duration)
}
Variant:
const (
max = 5
timeout = 3 * time.Second
)
func main() {
_, cancel := context.WithCancel(context.Background())
defer cancel()
time.AfterFunc(timeout, func() {
fmt.Println("operation timed out")
cancel()
os.Exit(0)
})
fmt.Println("working on it...")
result := slowOperation()
fmt.Println(result)
}
func slowOperation() string {
rand.Seed(time.Now().Unix())
duration := rand.Intn(max)
time.Sleep(time.Duration(duration) * time.Second)
return fmt.Sprintf("operation completed in %v seconds", duration)
}
Variant without context:
const (
max = 5
timeout = 3 * time.Second
)
func main() {
result := make(chan string)
go func() {
fmt.Println("working on it...")
result <- slowOperation()
}()
select {
case r := <-result:
fmt.Println(r)
case <-time.After(timeout):
fmt.Println("operation timed out")
}
}
func slowOperation() string {
rand.Seed(time.Now().Unix())
duration := rand.Intn(max)
time.Sleep(time.Duration(duration) * time.Second)
return fmt.Sprintf("operation completed in %v seconds", duration)
}
Example capturing cancellation signals:
const max = 5
func main() {
_, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
fmt.Println("operation concelled")
cancel()
os.Exit(0)
}()
fmt.Println("working on it...")
result := slowOperation()
fmt.Println(result)
}
func slowOperation() string {
rand.Seed(time.Now().Unix())
duration := rand.Intn(max)
time.Sleep(time.Duration(duration) * time.Second)
return fmt.Sprintf("operation completed in %v seconds", duration)
}
When a function accepts a context as a parameter, by convention it should be the first parameter.