Lab 13: Context & Cancellation

Objective

Use context.Context for cancellation, timeouts, deadlines, and request-scoped values. Propagate context through goroutines and HTTP handlers.

Time

30 minutes

Prerequisites

  • Lab 07 (Goroutines & Channels), Lab 11 (HTTP)

Tools

  • Docker image: zchencow/innozverse-go:latest


Lab Instructions

Step 1: Context Basics

docker run --rm zchencow/innozverse-go:latest go run - << 'EOF'
package main

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

func doWork(ctx context.Context, name string) error {
    select {
    case <-time.After(50 * time.Millisecond):
        fmt.Printf("[%s] completed\n", name)
        return nil
    case <-ctx.Done():
        fmt.Printf("[%s] cancelled: %v\n", name, ctx.Err())
        return ctx.Err()
    }
}

func main() {
    // context.Background() — root context, never cancelled
    bg := context.Background()
    fmt.Println("Background:", bg)

    // WithCancel — manual cancellation
    ctx, cancel := context.WithCancel(bg)
    defer cancel() // always call cancel to release resources

    go func() {
        time.Sleep(20 * time.Millisecond)
        fmt.Println("Cancelling...")
        cancel()
    }()

    if err := doWork(ctx, "Task1"); err != nil {
        fmt.Println("Task1 error:", err)
    }

    // WithTimeout — auto-cancel after duration
    ctx2, cancel2 := context.WithTimeout(bg, 100*time.Millisecond)
    defer cancel2()

    if err := doWork(ctx2, "Task2-fast"); err != nil {
        fmt.Println("Task2 error:", err)
    }

    ctx3, cancel3 := context.WithTimeout(bg, 10*time.Millisecond)
    defer cancel3()
    if err := doWork(ctx3, "Task3-slow"); err != nil {
        fmt.Println("Task3 error:", err)
    }

    // WithDeadline — cancel at specific time
    deadline := time.Now().Add(5 * time.Millisecond)
    ctx4, cancel4 := context.WithDeadline(bg, deadline)
    defer cancel4()
    fmt.Println("Deadline:", ctx4.Deadline())
    doWork(ctx4, "Task4-deadline")
}
EOF

💡 Always call cancel() — even if the context already expired. Not calling it leaks goroutines and memory. The idiomatic pattern is ctx, cancel := context.WithTimeout(parent, d); defer cancel(). Context cancellation propagates downward: cancelling a parent cancels all children automatically.

📸 Verified Output:


Step 2: Context Values

💡 Always use typed keys for context.WithValue — using a plain string like "user_id" as a key can collide with other packages. Using type contextKey string creates a distinct type that can't be accidentally matched by other packages using a plain string key.

📸 Verified Output:


Steps 3–8: Propagation, HTTP context, Timeout wrapper, Pipeline cancel, graceful shutdown, Capstone

📸 Verified Output:


Summary

Function
Purpose

context.Background()

Root context, never cancelled

context.WithCancel(parent)

Manual cancellation

context.WithTimeout(parent, d)

Auto-cancel after duration

context.WithDeadline(parent, t)

Cancel at specific time

context.WithValue(parent, k, v)

Attach request-scoped values

ctx.Done()

Channel closed when context cancelled

ctx.Err()

context.Canceled or context.DeadlineExceeded

Further Reading

Last updated