# Golang Concurrency #9 - Context and Cancellations

# Introduction

Context solves the problem: "How do I gracefully stop operations or set timeouts across multiple goroutines?"

# Key Insight

Context provides **cooperative cancellation** -&gt; you control when and how to check for cancellation, not abrupt force-killing.

# Creating Contexts

## 1\. WithTimeout -&gt; Automatic cancellation after duration

```go
// Cancel automatically after 3 seconds
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()  // Always call cancel to free resources
```

## 2\. WithCancel -&gt; Manual cancellation

```go
// Cancel manually when you want
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Later...
cancel()  // Trigger cancellation
```

## 3\. Background -&gt; Root context

```go
ctx := context.Background()  // Never cancelled, starting point
```

# How Cancellation Works

## The cooperative pattern

```go
func worker(ctx context.Context, name string) {
    for {  // Infinite loop -> YOU control when to exit
        select {
        case <-ctx.Done():  // "Did someone cancel me?"
            fmt.Printf("%s: Got cancellation signal\n", name)
            cleanup()  // YOU decide what cleanup to do
            fmt.Printf("%s: Cleaned up, exiting gracefully\n", name)
            return  // YOU decide when to exit
        default:  // "No cancellation signal, keep working"
            fmt.Printf("%s: working...\n", name)
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    // Pass same context to multiple workers
    go worker(ctx, "Worker 1")
    go worker(ctx, "Worker 2")
    go worker(ctx, "Worker 3")

    time.Sleep(2 * time.Second)
    cancel()  // Signal ALL workers to cancel
    time.Sleep(1 * time.Second)  // Give them time to clean up
}
```

# What happens step by step

1. **Workers start:** Enter infinite `for` loop
    
2. **Select checks:** "Is `ctx.Done()` ready to receive?"
    
3. **If NOT cancelled:** Goes to `default`, does work, loops again
    
4. **When** `cancel()` called: `ctx.Done()` channel gets closed
    
5. **Next time workers hit select:** `ctx.Done()` case executes
    
6. **Workers clean up:** Do whatever cleanup they want
    
7. **Workers exit:** When THEY decide to `return`
    

# Different checking patterns

## Pattern 1 -&gt; Check every iteration

```go
func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return  // Check every loop iteration
        default:
        }
        doWork()
    }
}
```

## Pattern 2 -&gt; Check periodically

```go
func worker(ctx context.Context) {
    for i := 0; i < 1000000; i++ {
        processItem(i)

        // Only check cancellation every 1000 items
        if i%1000 == 0 {
            select {
            case <-ctx.Done():
                fmt.Printf("Cancelled at item %d\n", i)
                return
            default:
            }
        }
    }
}
```

## Pattern 3 -&gt; Racing work vs cancellation

```go
func fetchData(ctx context.Context) (string, error) {
    result := make(chan string)

    go func() {
        time.Sleep(5 * time.Second)  // Simulate slow work
        result <- "data"
    }()

    select {
    case data := <-result:
        return data, nil  // Work finished first
    case <-ctx.Done():
        return "", ctx.Err()  // Cancellation won first
    }
}
```

## Timeout Example

```go
func main() {
    // Context automatically cancels after 2 seconds
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    result := make(chan string)
    go slowWork(ctx, result)

    select {
    case data := <-result:
        fmt.Println("Got result:", data)
    case <-ctx.Done():
        fmt.Println("Timeout! Work took too long")
        fmt.Println("Error:", ctx.Err())  // context.DeadlineExceeded
    }
}

func slowWork(ctx context.Context, result chan string) {
    select {
    case <-time.After(3 * time.Second):  // Work takes 3 seconds
        result <- "finished work"
    case <-ctx.Done():  // But context times out after 2 seconds
        fmt.Println("Work cancelled due to timeout")
        return
    }
}
```

## Context Propagation

```go
func handleRequest(ctx context.Context) {
    // Pass context down the call chain
    data := fetchFromAPI(ctx)
    processData(ctx, data)
    saveToDatabase(ctx, data)
}

func fetchFromAPI(ctx context.Context) string {
    // This function also respects the same cancellation
    select {
    case <-time.After(1 * time.Second):
        return "api data"
    case <-ctx.Done():
        return ""
    }
}
```

# Key Points

## 1\. Cooperative Nature

Context doesn't force-kill anything. Your goroutines choose when to check and how to respond.

## 2\. Graceful Shutdown

You can clean up, save state, close files, etc. before exiting.

## 3\. Propagation

One context can control an entire tree of operations.

## 4\. Channel Syntax

`<-ctx.Done()` receives from the Done() channel, which becomes ready when context is cancelled.

## 5\. Always call `cancel()`

Even with timeout contexts, call `cancel()` to free resources.
