The context package in Go has recently introduced cause-aware context cancellation functions. This update includes the WithCancelCause
, WithDeadlineCause
, and WithTimeoutCause
functions, as well as the Cause
function for retrieving the cause of context cancellation.
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)
This function is analogous to WithCancel
, but it returns a CancelCauseFunc
that allows setting an error as the cause of cancellation.
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)
Combining features of WithDeadline
with the ability to provide a cause for context cancellation.
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)
Similar to WithTimeout
, this function allows specifying a cause for context cancellation.
func Cause(c Context) error
Retrieves the cause of a context’s cancellation.
Understanding the Cause
Functionality
Link to heading
The purpose of the Cause
function is straightforward: it returns a non-nil error explaining the reason for the cancellation of a given context (c
). If the context has not been canceled, the function returns nil.
Exploring Examples Link to heading
Let’s delve into practical examples to illustrate the functionality of the Cause
function.
Example 1: Context Canceled with Error Link to heading
In this scenario, we create a parent context and a child context, explicitly cancel the child context, and then use the Cause
function to retrieve the cancellation error.
package main
import (
"context"
"errors"
"fmt"
)
func main() {
parentContext := context.Background()
childContext, cancel := context.WithCancelCause(parentContext)
cancel(errors.New("the context was explicitly canceled"))
err := context.Cause(childContext)
if err != nil {
fmt.Printf("Context canceled with error: %v\n", err)
} else {
fmt.Println("Context not canceled yet.")
}
}
Output:
➜ go run cause.go
Context canceled with error: the context was explicitly canceled
In this example, the Cause
function correctly identifies the cancellation error.
Example 2: Context Not Canceled Yet Link to heading
Here, we explore a scenario where the context has not been canceled, and the Cause
function correctly returns nil.
package main
import (
"context"
"fmt"
)
func main() {
parentContext := context.Background()
err := context.Cause(parentContext)
if err != nil {
fmt.Printf("Context canceled with error: %v\n", err)
} else {
fmt.Println("Context not canceled yet.")
}
}
Output:
➜ go run cause.go
Context not canceled yet.
In this example, the Cause
function recognizes that the context has not been canceled and returns nil.
Example 3: Hierarchy of Contexts Link to heading
The Cause
function exhibits an intriguing feature: “The first cancellation of c or one of its parents sets the cause.” Let’s explore this by creating a hierarchy of contexts.
package main
import (
"context"
"fmt"
"time"
)
func main() {
backgroundContext := context.Background()
parentContext, cancelParent := context.WithCancelCause(backgroundContext)
childContext, _ := context.WithCancel(parentContext)
cancelParent(errors.New("the parent context was cancelled"))
err := context.Cause(childContext)
if err != nil {
fmt.Printf("Context canceled with error: %v\n", err)
} else {
fmt.Println("Context not canceled yet.")
}
time.Sleep(3 * time.Second)
}
Here, despite canceling the parent context, we can retrieve the cause of cancellation from the child context.