Mastering Go’s Context: Cancellation, Timeouts, and Value Propagation
This article explains Go’s context package, covering its purpose for managing goroutine lifecycles, the key interfaces and implementations such as emptyCtx, cancelCtx, timerCtx, and valueCtx, and demonstrates how to use WithCancel, WithDeadline, WithTimeout, and WithValue to control execution, propagate cancellations, and pass values across call chains.
Context Overview
The context package, introduced in Go 1.7, provides a way to control the lifetime of goroutines and to pass request-scoped values, cancellation signals, and deadlines through a tree of related operations.
Core Interfaces
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
type canceler interface {
cancel(removeFromParent bool, err error) // close operation
Done() <-chan struct{} // read‑only channel
}Implementations
emptyCtx – the base context that never cancels and returns no values.
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return }
func (*emptyCtx) Done() <-chan struct{} { return nil }
func (*emptyCtx) Err() error { return nil }
func (*emptyCtx) Value(key interface{}) interface{} { return nil }
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context { return background }
func TODO() Context { return todo }cancelCtx – holds a parent context, a mutex, a done channel, child cancelers, and an error describing why it was canceled.
type cancelCtx struct {
Context // parent context
mu sync.Mutex
done chan struct{}
children map[canceler]struct{}
err error
}
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
for child := range c.children {
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c)
}
}timerCtx – extends cancelCtx with a timer and a deadline.
type timerCtx struct {
cancelCtx // embedded cancel context
timer *time.Timer
deadline time.Time
}
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}valueCtx – stores a key/value pair and forwards look‑ups to its parent.
type valueCtx struct {
Context // parent context
key, val interface{}
}
func WithValue(parent Context, key, val interface{}) Context {
if key == nil { panic("nil key") }
if !reflect.TypeOf(key).Comparable() { panic("key is not comparable") }
return &valueCtx{parent, key, val}
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key { return c.val }
return c.Context.Value(key) // search upward
}Using Context
WithCancel creates a cancellable child context.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}WithDeadline sets an absolute deadline; if the parent’s deadline is sooner, it falls back to WithCancel.
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
c := &timerCtx{cancelCtx: newCancelCtx(parent), deadline: d}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded)
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded) })
}
return c, func() { c.cancel(true, Canceled) }
}WithTimeout is a convenience wrapper that uses a relative duration.
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}WithValue attaches a key/value pair to a context.
func WithValue(parent Context, key, val interface{}) Context {
// implementation shown in valueCtx section
}These functions together enable developers to build a cancellation tree, enforce timeouts, and pass request‑scoped data without leaking resources.
TiPaiPai Technical Team
At TiPaiPai, we focus on building engineering teams and culture, cultivating technical insights and practice, and fostering sharing, growth, and connection.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
