Internals of the context package

Who am I?

Damiano Petrungaro

Who am I?

Staff Engineer @Odin

Who am I?

Milan, Italy

Who am I?

Manga and anime

The context package

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

package context

func Background() Context { ... }

func TODO() Context { ... }

func WithValue(parent Context, key, val any) Context { ... }

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { ... }

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { ... }

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { ... }

func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) { ... }

func Cause(c Context) error { ... }

Using the context pkg

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key any) any
}
package main

import "context"

func main() {
	ctx := context.Background()
	// ...
}

Background and TODO

Internals

package main

import "context"

func main() {
	ctx := context.Background()
	// ...
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L205
var (
	background = new(emptyCtx)
)

func Background() Context {
	return background
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L177
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
}

Background

Internals

package main

import "context"

func main() {
	ctx := context.TODO()
	// ...
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L205
var (
	todo = new(emptyCtx)
)

// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
	return todo
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L177
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
}

Internals

TODO

Using the context pkg

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

package main

import "context"

func main() {
	ctx := context.Background()

	ctx = context.WithValue(ctx, "one", 1)

	ctx = context.WithValue(ctx, "two", 2)

	ctx.Value("two")

	ctx.Value("one")

	ctx.Value("zero")
}

Value

Internals

package main

import "context"

func main() {
	ctx := context.Background()

	ctx = context.WithValue(ctx, "one", 1)

	ctx = context.WithValue(ctx, "two", 2)

	ctx.Value("two")

	ctx.Value("one")

	ctx.Value("zero")
}

Value

Internals

emptyCtx

Value

Visualizing the internals

package main

import "context"

func main() {
	ctx := context.Background()

	ctx = context.WithValue(ctx, "one", 1)

	ctx = context.WithValue(ctx, "two", 2)

	ctx.Value("two")

	ctx.Value("one")

	ctx.Value("zero")
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L583
func WithValue(parent Context, key, val any) Context {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if key == nil {
		panic("nil key")
	}
	if !reflectlite.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
	return &valueCtx{parent, key, val}
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L598
type valueCtx struct {
    Context
    key, val any
}

Value

Internals

emptyCtx

valueCtx

{key:"one",val:1}

Value

Visualizing the internals

package main

import "context"

func main() {
	ctx := context.Background()

	ctx = context.WithValue(ctx, "one", 1)

	ctx = context.WithValue(ctx, "two", 2)

	ctx.Value("two")

	ctx.Value("one")

	ctx.Value("zero")
}

Value

Internals

emptyCtx

valueCtx

{key:"one",val:1}

valueCtx

{key:"two",val:2}

Value

Visualizing the internals

package main

import "context"

func main() {
	ctx := context.Background()

	ctx = context.WithValue(ctx, "one", 1)

	ctx = context.WithValue(ctx, "two", 2)

	ctx.Value("two")

	ctx.Value("one")

	ctx.Value("zero")
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L598
type valueCtx struct {
    Context
    key, val any
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L622
func (c *valueCtx) Value(key any) any {
    if c.key == key {
        return c.val
    }
    return value(c.Context, key)
}

func value(c Context, key any) any {
    for {
        switch ctx := c.(type) {
            case *valueCtx:
                if key == ctx.key {
                    return ctx.val
                }
                c = ctx.Context
            // omitted code that behaves as previous line `c = ctx.Context`
            case *emptyCtx:
                return nil
            default:
                return c.Value(key)
        }
    }
}

Value

Internals

emptyCtx

valueCtx

{key:"one",val:1}

valueCtx

{key:"two",val:2}

🔍

Value("two")

Value

Visualizing the internals

package main

import "context"

func main() {
	ctx := context.Background()

	ctx = context.WithValue(ctx, "one", 1)

	ctx = context.WithValue(ctx, "two", 2)

	ctx.Value("two")

	ctx.Value("one")

	ctx.Value("zero")
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L598
type valueCtx struct {
    Context
    key, val any
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L622
func (c *valueCtx) Value(key any) any {
    if c.key == key {
        return c.val
    }
    return value(c.Context, key)
}

func value(c Context, key any) any {
    for {
        switch ctx := c.(type) {
            case *valueCtx:
                if key == ctx.key {
                    return ctx.val
                }
                c = ctx.Context
            // omitted code that behaves as previous line `c = ctx.Context`
            case *emptyCtx:
                return nil
            default:
                return c.Value(key)
        }
    }
}

Value

Internals

emptyCtx

valueCtx

{key:"one",val:1}
Value("one")

🔍


valueCtx

{key:"two",val:2}

🔍

Value("one")

Value

Visualizing the internals

package main

import "context"

func main() {
	ctx := context.Background()

	ctx = context.WithValue(ctx, "one", 1)

	ctx = context.WithValue(ctx, "two", 2)

	ctx.Value("two")

	ctx.Value("one")

	ctx.Value("zero")
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L598
type valueCtx struct {
    Context
    key, val any
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L622
func (c *valueCtx) Value(key any) any {
    if c.key == key {
        return c.val
    }
    return value(c.Context, key)
}

func value(c Context, key any) any {
    for {
        switch ctx := c.(type) {
            case *valueCtx:
                if key == ctx.key {
                    return ctx.val
                }
                c = ctx.Context
            // omitted code that behaves as previous line `c = ctx.Context`
            case *emptyCtx:
                return nil
            default:
                return c.Value(key)
        }
    }
}

Value

Internals

emptyCtx

valueCtx

{key:"one",val:1}
Value("zero")

🔍


valueCtx

{key:"two",val:2}

🔍

Value("zero")
Value("zero")

🔍

Value

Visualizing the internals

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

Using the context pkg

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}

Cancel

Internals

Cancel

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}

Internals

emptyCtx

Cancel

Visualizing the internals

Cancel

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L238
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L271
func withCancel(parent Context) *cancelCtx {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	c := newCancelCtx(parent)
	propagateCancel(parent, c)
	return c
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L296
func newCancelCtx(parent Context) *cancelCtx {
	return &cancelCtx{Context: parent}
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L304
func propagateCancel(parent Context, child canceler) {
	// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L318
	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		// ...
		// Here the child is stored in the parent, initialising a map if this is the first one.
		// The map is used in favor of a slice for performance purposes mainly as for searching a single child will have an impact of O(1).  
		if p.children == nil {
			p.children = make(map[canceler]struct{})
		}
		p.children[child] = struct{}{}
		p.mu.Unlock()
	}
	// ...
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L396
type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}

emptyCtx

cancelCtx

Cancel

Visualizing the internals

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}
package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}

Cancel

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L238
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L271
func withCancel(parent Context) *cancelCtx {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	c := newCancelCtx(parent)
	propagateCancel(parent, c)
	return c
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L296
func newCancelCtx(parent Context) *cancelCtx {
	return &cancelCtx{Context: parent}
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L304
func propagateCancel(parent Context, child canceler) {
	// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L318
	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		// ...
		// Here the child is stored in the parent, initialising a map if this is the first one.
		// The map is used in favor of a slice for performance purposes mainly as for searching a single child will have an impact of O(1).  
		if p.children == nil {
			p.children = make(map[canceler]struct{})
		}
		p.children[child] = struct{}{}
		p.mu.Unlock()
	}
	// ...
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L396
type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}

emptyCtx

cancelCtx

func work

cancelCtx

cancelCtx

func main

Cancel

Visualizing the internals

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}
package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}

Cancel

Internals

emptyCtx

cancelCtx

func work

cancelCtx

cancelCtx

func slowFn

valueCtx

valueCtx

{key:"one",val:1}
{key:"one",val:1}

func main

Cancel

Visualizing the internals

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}

Cancel

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}

Internals

emptyCtx

cancelCtx

func work

cancelCtx

cancelCtx

func slowFn

valueCtx

valueCtx

{key:"one",val:1}
{key:"one",val:1}

valueCtx

{key:"two",val:2}

valueCtx

{key:"two",val:2}

func main

Cancel

Visualizing the internals

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(/* TODO */, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}

Cancel

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L396
type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}

func (c *cancelCtx) Value(key any) any {
	if key == &cancelCtxKey {
		return c
	}
	return value(c.Context, key)
}

func (c *cancelCtx) Done() <-chan struct{} {
	d := c.done.Load()
	if d != nil {
		return d.(chan struct{})
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	d = c.done.Load()
	if d == nil {
		d = make(chan struct{})
		c.done.Store(d)
	}
	return d.(chan struct{})
}

func (c *cancelCtx) Err() error {
	c.mu.Lock()
	err := c.err
	c.mu.Unlock()
	return err
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L598
type valueCtx struct {
    Context
    key, val any
}
package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(4 * time.Second, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}

Cancel

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L238
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L453
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
    // ...
	
    // Note: this code section is simplified from the original one for reading purposes.
    // TLDR:
    // Sets the reason why the context was canceled.
    // If the error is set and the context is marked again as canceled it will not take any action,
    // in this case, is set as `context.Canceled` 
    // defined here https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L163
    c.err = err

    // ...
    // If the context has a channel in the `done` field
    // it means that a goroutine _may_ be interested in knowing
    // the context is now done, so the channel is closed.
    d, _ := c.done.Load().(chan struct{})
    close(d)

    // ...
    // Cancels all the registered children.
    for child := range c.children {
        child.cancel(false, err, cause)
    }
    c.children = nil
    // ...
    if removeFromParent {
        removeChild(c.Context, c)
    }
}

emptyCtx

cancelCtx

func work

cancelCtx

cancelCtx

func slowFn

valueCtx

valueCtx

{key:"one",val:1}
{key:"one",val:1}

valueCtx

{key:"two",val:2}

valueCtx

{key:"two",val:2}

func main

{err: Cancelled}
{err: Cancelled}
defer cancel()
defer cancel()
defer cancel()
{err: Cancelled}

Cancel

Visualizing the internals

Cancel

Internals

package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(2 * time.Second, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L238
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L453
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
    // ...
	
    // Note: this code section is simplified from the original one for reading purposes.
    // TLDR:
    // Sets the reason why the context was canceled.
    // If the error is set and the context is marked again as canceled it will not take any action,
    // in this case, is set as `context.Canceled` 
    // defined here https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L163
    c.err = err

    // ...
    // If the context has a channel in the `done` field
    // it means that a goroutine _may_ be interested in knowing
    // the context is now done, so the channel is closed.
    d, _ := c.done.Load().(chan struct{})
    close(d)

    // ...
    // Cancels all the registered children.
    for child := range c.children {
        child.cancel(false, err, cause)
    }
    c.children = nil
    // ...
    if removeFromParent {
        removeChild(c.Context, c)
    }
}
package main

import (
    "context"
    "log"
    "sync"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    wg := sync.WaitGroup{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(i int) {
            work(ctx, i)
            wg.Done()
        }(i)
    }

    time.AfterFunc(2 * time.Second, func() { cancel() })
    wg.Wait()
    log.Println("completed")
}

func work(ctx context.Context, i int) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    slowFn(ctx, i)
}

func slowFn(ctx context.Context, i int) {
    ctx = context.WithValue(ctx, "one", 1)
    ctx = context.WithValue(ctx, "two", 2)

    log.Printf("slow function %d started\n", i)
    select {
    case <-time.Tick(3 * time.Second):
        log.Printf("slow function %d finished\n", i)
    case <-ctx.Done():
        log.Printf("slow function %d too slow: %s\n", i, ctx.Err())
    }
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L396
type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}

func (c *cancelCtx) Value(key any) any {
	if key == &cancelCtxKey {
		return c
	}
	return value(c.Context, key)
}

func (c *cancelCtx) Done() <-chan struct{} {
	d := c.done.Load()
	if d != nil {
		return d.(chan struct{})
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	d = c.done.Load()
	if d == nil {
		d = make(chan struct{})
		c.done.Store(d)
	}
	return d.(chan struct{})
}

func (c *cancelCtx) Err() error {
	c.mu.Lock()
	err := c.err
	c.mu.Unlock()
	return err
}

emptyCtx

cancelCtx

func work

cancelCtx

cancelCtx

func slowFn

valueCtx

valueCtx

{key:"one",val:1}
{key:"one",val:1}

valueCtx

{key:"two",val:2}

valueCtx

{key:"two",val:2}

func main

{err: Cancelled}
{err: Cancelled}
cancel()
{err: Cancelled}
{key:"one",val:1,err:Cancelled}
{key:"two",val:2,err:Cancelled}
{key:"one",val:1,err:Cancelled}
{key:"two",val:2,err:Cancelled}

Cancel

Visualizing the internals

Go 1.20 update

package context

func Background() Context { ... }

func TODO() Context { ... }

func WithValue(parent Context, key, val any) Context { ... }

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { ... }

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { ... }

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { ... }

func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) { ... }

func Cause(c Context) error { ... }

WithCancelCause

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L266
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) {
	c := withCancel(parent)
	return c, func(cause error) { c.cancel(true, Canceled, cause) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L453
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
	// ...
	if cause == nil {
		cause = err
	}
	c.err = err
	c.cause = cause
	// ...
}

Cause

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L286
func Cause(c Context) error {
	if cc, ok := c.Value(&cancelCtxKey).(*cancelCtx); ok {
		cc.mu.Lock()
		defer cc.mu.Unlock()
		return cc.cause
	}
	return nil
}

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

Using the context pkg

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

Timer

emptyCtx

Visualizing the internals

Timer

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L494
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	// If the parent deadline is prior to the new one we're creating
	// it returns a cancelCtx since we don't need the overhead of a `timerCtx`.
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		return WithCancel(parent)
	}
   
	// Creates a timerCtx.
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}

	// Ensures the timerCtx will be canceled when the parent is marked as such.
	propagateCancel(parent, c)
   
	// If the deadline is already passed (maybe you passed a time.Time in the past?)
	// it immediately cancels timerCtx and returns a CancelFunc that will do nothing when invoked.
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
   
	// Creates a timer that will mark the context done as `DeadlineExceeded` when the deadline is reached.
	c.timer = time.AfterFunc(dur, func() {
		c.cancel(true, DeadlineExceeded, nil)
	})

	// Returns the timerCtx and a CancelFunc that can be called before the timer is triggered.
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L567
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L525
type timerCtx struct {
	*cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L542
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
	c.cancelCtx.cancel(false, err, cause)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

emptyCtx

timerCtx

{deadline: now+7s}

Visualizing the internals

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

Timer

emptyCtx

timerCtx

func work

timerCtx

func main

{deadline: now+1s}
{deadline: now+7s}

Visualizing the internals

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L494
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	// If the parent deadline is prior to the new one we're creating
	// it returns a cancelCtx since we don't need the overhead of a `timerCtx`.
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		return WithCancel(parent)
	}
   
	// Creates a timerCtx.
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}

	// Ensures the timerCtx will be canceled when the parent is marked as such.
	propagateCancel(parent, c)
   
	// If the deadline is already passed (maybe you passed a time.Time in the past?)
	// it immediately cancels timerCtx and returns a CancelFunc that will do nothing when invoked.
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded) // deadline has already passed
		return c, func() { c.cancel(false, Canceled) }
	}
   
	// Creates a timer that will mark the context done as `DeadlineExceeded` when the deadline is reached.
	c.timer = time.AfterFunc(dur, func() {
		c.cancel(true, DeadlineExceeded)
	})

	// Returns the timerCtx and a CancelFunc that can be called before the timer is triggered.
	return c, func() { c.cancel(true, Canceled) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L567
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L525
type timerCtx struct {
	*cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

Timer

emptyCtx

timerCtx

func work

timerCtx

func main

cancelCtx

{deadline: now+1s}
{deadline: now+7s}

Visualizing the internals

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L494
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	// If the parent deadline is prior to the new one we're creating
	// it returns a cancelCtx since we don't need the overhead of a `timerCtx`.
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		return WithCancel(parent)
	}
   
	// Creates a timerCtx.
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}

	// Ensures the timerCtx will be canceled when the parent is marked as such.
	propagateCancel(parent, c)
   
	// If the deadline is already passed (maybe you passed a time.Time in the past?)
	// it immediately cancels timerCtx and returns a CancelFunc that will do nothing when invoked.
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
   
	// Creates a timer that will mark the context done as `DeadlineExceeded` when the deadline is reached.
	c.timer = time.AfterFunc(dur, func() {
		c.cancel(true, DeadlineExceeded, nil)
	})

	// Returns the timerCtx and a CancelFunc that can be called before the timer is triggered.
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L567
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L525
type timerCtx struct {
	*cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L542
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
	c.cancelCtx.cancel(false, err, cause)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

Timer

emptyCtx

timerCtx

func work

timerCtx

timerCtx

func main

cancelCtx

{deadline: now+1s}
{deadline: now-1s}
{deadline: now+7s}
{err:DeadlineExceeded}

Visualizing the internals

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

Timer

emptyCtx

timerCtx

func work

timerCtx

timerCtx

func main

cancelCtx

valueCtx

{key:"id",val:1}

valueCtx

{key:"id",val:2}
{deadline: now+1s}
{deadline: now-1s}
{deadline: now+7s}

valueCtx

{key:"id",val:3}
{err:DeadlineExceeded}

Visualizing the internals

func slowTask

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L494
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	// If the parent deadline is prior to the new one we're creating
	// it returns a cancelCtx since we don't need the overhead of a `timerCtx`.
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		return WithCancel(parent)
	}
   
	// Creates a timerCtx.
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}

	// Ensures the timerCtx will be canceled when the parent is marked as such.
	propagateCancel(parent, c)
   
	// If the deadline is already passed (maybe you passed a time.Time in the past?)
	// it immediately cancels timerCtx and returns a CancelFunc that will do nothing when invoked.
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
   
	// Creates a timer that will mark the context done as `DeadlineExceeded` when the deadline is reached.
	c.timer = time.AfterFunc(dur, func() {
		c.cancel(true, DeadlineExceeded, nil)
	})

	// Returns the timerCtx and a CancelFunc that can be called before the timer is triggered.
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L567
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L525
type timerCtx struct {
	*cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L542
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
	c.cancelCtx.cancel(false, err, cause)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}
package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L494
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	// If the parent deadline is prior to the new one we're creating
	// it returns a cancelCtx since we don't need the overhead of a `timerCtx`.
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		return WithCancel(parent)
	}
   
	// Creates a timerCtx.
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}

	// Ensures the timerCtx will be canceled when the parent is marked as such.
	propagateCancel(parent, c)
   
	// If the deadline is already passed (maybe you passed a time.Time in the past?)
	// it immediately cancels timerCtx and returns a CancelFunc that will do nothing when invoked.
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
   
	// Creates a timer that will mark the context done as `DeadlineExceeded` when the deadline is reached.
	c.timer = time.AfterFunc(dur, func() {
		c.cancel(true, DeadlineExceeded, nil)
	})

	// Returns the timerCtx and a CancelFunc that can be called before the timer is triggered.
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L567
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L525
type timerCtx struct {
	*cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L542
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
	c.cancelCtx.cancel(false, err, cause)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

Timer

emptyCtx

timerCtx

func work

timerCtx

timerCtx

func main

cancelCtx

valueCtx

{key:"id",val:1}

valueCtx

{key:"id",val:2}
{deadline: now+1s}
{deadline: now-1s}
{deadline: now+7s}

valueCtx

{key:"id",val:3}
{err:DeadlineExceeded}
defer cancel()

Visualizing the internals

func slowTask

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L494
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	// If the parent deadline is prior to the new one we're creating
	// it returns a cancelCtx since we don't need the overhead of a `timerCtx`.
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		return WithCancel(parent)
	}
   
	// Creates a timerCtx.
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}

	// Ensures the timerCtx will be canceled when the parent is marked as such.
	propagateCancel(parent, c)
   
	// If the deadline is already passed (maybe you passed a time.Time in the past?)
	// it immediately cancels timerCtx and returns a CancelFunc that will do nothing when invoked.
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
   
	// Creates a timer that will mark the context done as `DeadlineExceeded` when the deadline is reached.
	c.timer = time.AfterFunc(dur, func() {
		c.cancel(true, DeadlineExceeded, nil)
	})

	// Returns the timerCtx and a CancelFunc that can be called before the timer is triggered.
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L567
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L525
type timerCtx struct {
	*cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L542
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
	c.cancelCtx.cancel(false, err, cause)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

Timer

emptyCtx

timerCtx

func work

timerCtx

timerCtx

func main

cancelCtx

valueCtx

{key:"id",val:1}

valueCtx

{key:"id",val:2}
{deadline: now+1s}
{deadline: now-1s}
{deadline: now+7s}

valueCtx

{key:"id",val:3}
{err:DeadlineExceeded}
{err:Cancelled}
cancel()

Visualizing the internals

func slowTask

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L494
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	// If the parent deadline is prior to the new one we're creating
	// it returns a cancelCtx since we don't need the overhead of a `timerCtx`.
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		return WithCancel(parent)
	}
   
	// Creates a timerCtx.
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}

	// Ensures the timerCtx will be canceled when the parent is marked as such.
	propagateCancel(parent, c)
   
	// If the deadline is already passed (maybe you passed a time.Time in the past?)
	// it immediately cancels timerCtx and returns a CancelFunc that will do nothing when invoked.
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
		return c, func() { c.cancel(false, Canceled, nil) }
	}
   
	// Creates a timer that will mark the context done as `DeadlineExceeded` when the deadline is reached.
	c.timer = time.AfterFunc(dur, func() {
		c.cancel(true, DeadlineExceeded, nil)
	})

	// Returns the timerCtx and a CancelFunc that can be called before the timer is triggered.
	return c, func() { c.cancel(true, Canceled, nil) }
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L567
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L525
type timerCtx struct {
	*cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L542
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
	c.cancelCtx.cancel(false, err, cause)
	if removeFromParent {
		// Remove this timerCtx from its parent cancelCtx's children.
		removeChild(c.cancelCtx.Context, c)
	}
	c.mu.Lock()
	if c.timer != nil {
		c.timer.Stop()
		c.timer = nil
	}
	c.mu.Unlock()
}

Timer

emptyCtx

timerCtx

func work

timerCtx

timerCtx

func main

cancelCtx

func slowTask

valueCtx

{key:"id",val:1}

valueCtx

{key:"id",val:2}
{deadline: now+1s}
{deadline: now-1s}
{deadline: now+7s}

valueCtx

{key:"id",val:3}
{err:DeadlineExceeded}
{err:Cancelled}
time AfterFunc
{err:DeadlineExceeded}
{err:DeadlineExceeded}

Visualizing the internals

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
	defer cancel()

	wg := &sync.WaitGroup{}

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go work(ctx, wg, i)
	}

	wg.Wait()
	fmt.Println("done")
}

func work(ctx context.Context, wg *sync.WaitGroup, id int) {
	defer wg.Done()

	switch id {
	case 0:
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		time.AfterFunc(500*time.Millisecond, func() { cancel() })
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 1 second", id))
	case 1:
		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of 10 second", id))
	case 2:
		ctx, cancel := context.WithTimeout(ctx, -1*time.Second)
		defer cancel()
		slowTask(ctx, id, fmt.Sprintf("worker %d had a timeout of -1 second", id))
	}
}

func slowTask(ctx context.Context, id int, prefix string) {
	ctx = context.WithValue(ctx, "id", id)

	fmt.Printf("%s: started\n", prefix)
	select {
	case <-time.Tick(15 * time.Second):
		fmt.Printf("%s: finished\n", prefix)
	case <-ctx.Done():
		fmt.Printf("%s: too slow... returning: %s\n", prefix, ctx.Err())
	}
}

Timer

Internals

package context

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L525
type timerCtx struct {
	*cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

// https://github.com/golang/go/blob/go1.20.5/src/context/context.go#L453
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
    // ...
    c.mu.Lock()
    if c.err != nil {
        c.mu.Unlock()
        return // already canceled
    }
    // ...
}

Recap

Contacts

Thank you

The internals of the context package

By Damiano Petrungaro

The internals of the context package

The internals of the context package

  • 431