Context and Cancellation
Early Stop
Code can check ctx.Err() during a loop and stop when cancellation appears.
early stop
Cancellation is cooperative: code checks the context and chooses when to stop.
Early Stop
early_stop.go
Replay: real traced execution (multi-file project)
package main
import (
"context"
"fmt"
)
func main() {
var stopAfter = 2
ctx, cancel := context.WithCancel(context.Background())
items := []string{"red", "green", "blue", "gold"}
processed := 0
for _, item := range items {
if ctx.Err() != nil {
break
}
processed++
fmt.Println("item=", item)
if processed == stopAfter {
cancel()
}
}
fmt.Println("processed=", processed)
fmt.Println("err=", ctx.Err())
}
package main
import (
"context"
"fmt"
)
func main() {
var stopAfter = 3
ctx, cancel := context.WithCancel(context.Background())
items := []string{"red", "green", "blue", "gold"}
processed := 0
for _, item := range items {
if ctx.Err() != nil {
break
}
processed++
fmt.Println("item=", item)
if processed == stopAfter {
cancel()
}
}
fmt.Println("processed=", processed)
fmt.Println("err=", ctx.Err())
}
package main
import (
"context"
"fmt"
)
func main() {
var stopAfter = 4
ctx, cancel := context.WithCancel(context.Background())
items := []string{"red", "green", "blue", "gold"}
processed := 0
for _, item := range items {
if ctx.Err() != nil {
break
}
processed++
fmt.Println("item=", item)
if processed == stopAfter {
cancel()
}
}
fmt.Println("processed=", processed)
fmt.Println("err=", ctx.Err())
}
stopAfter ← 2, ctx ← &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}
8func main() {9 var stopAfter→ 2 = 2 //@stopAfter=3, 410 ctx→ &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}, cancel→ (context.CancelFunc)(⟨addr A⟩) := context.WithCancel(context.Background())11 items→ []string{"red", "green", "blue", "gold"} := []string{"red", "green", "blue", "gold"}1213 processed→ 0 := 014 for _, item := range items {processed ← 1
pass 1 of 313processed := 014for _, item"red" := range items[]string{"red", "green", "blue", "gold"} {15 if ctx.Err() != nil {16 break17 }18 processed→ 1++19 fmt.Println("item=", item"red")20 if processed == stopAfter {outputitem= redAll 3 passes — pass 1 is the card above pass itemstopAfterctxprocessed1 "red" — — 0 → 1 2 "green" 2 — 1 → 2 3 "blue" — &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)} — if processed == stopAfter
19fmt.Println("item=", item)20if processed2 == stopAfter2 {21 cancel()22}if ctx.Err() != nil
14for _, item := range items {15 if ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err() != nil {16 break17 }fmt.Println("processed=", processed)
25 fmt.Println("processed=", processed2)26 fmt.Println("err=", ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err())27}outputprocessed= 2 err= context canceled
stopAfter ← 3, ctx ← &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}
8func main() {9 var stopAfter→ 3 = 310 ctx→ &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}, cancel→ (context.CancelFunc)(⟨addr A⟩) := context.WithCancel(context.Background())11 items→ []string{"red", "green", "blue", "gold"} := []string{"red", "green", "blue", "gold"}1213 processed→ 0 := 014 for _, item := range items {processed ← 1
pass 1 of 413processed := 014for _, item"red" := range items[]string{"red", "green", "blue", "gold"} {15 if ctx.Err() != nil {16 break17 }18 processed→ 1++19 fmt.Println("item=", item"red")20 if processed == stopAfter {outputitem= redAll 4 passes — pass 1 is the card above pass itemstopAfterctxprocessed1 "red" — — 0 → 1 2 "green" — — 1 → 2 3 "blue" 3 — 2 → 3 4 "gold" — &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)} — if processed == stopAfter
19fmt.Println("item=", item)20if processed3 == stopAfter3 {21 cancel()22}if ctx.Err() != nil
14for _, item := range items {15 if ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err() != nil {16 break17 }fmt.Println("processed=", processed)
25 fmt.Println("processed=", processed3)26 fmt.Println("err=", ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err())27}outputprocessed= 3 err= context canceled
stopAfter ← 4, ctx ← &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}
8func main() {9 var stopAfter→ 4 = 410 ctx→ &context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:interface {}(nil)}, children:map[context.canceler]struct {}(nil), err:error(nil), cause:error(nil)}, cancel→ (context.CancelFunc)(⟨addr A⟩) := context.WithCancel(context.Background())11 items→ []string{"red", "green", "blue", "gold"} := []string{"red", "green", "blue", "gold"}1213 processed→ 0 := 014 for _, item := range items {processed ← 1
pass 1 of 413processed := 014for _, item"red" := range items[]string{"red", "green", "blue", "gold"} {15 if ctx.Err() != nil {16 break17 }18 processed→ 1++19 fmt.Println("item=", item"red")20 if processed == stopAfter {outputitem= redAll 4 passes — pass 1 is the card above pass itemstopAfterprocessed1 "red" — 0 → 1 2 "green" — 1 → 2 3 "blue" — 2 → 3 4 "gold" 4 3 → 4 if processed == stopAfter
19fmt.Println("item=", item)20if processed4 == stopAfter4 {21 cancel()22}fmt.Println("processed=", processed)
25 fmt.Println("processed=", processed4)26 fmt.Println("err=", ctx&context.cancelCtx{Context:context.backgroundCtx{emptyCtx:context.emptyCtx{}}, mu:sync.Mutex{state:0, sema:0x0}, done:atomic.Value{v:(chan struct {})(⟨addr B⟩)}, children:map[context.canceler]struct {}(nil), err:(*errors.errorString)(⟨addr C⟩), cause:(*errors.errorString)(⟨addr C⟩)}.Err())27}outputprocessed= 4 err= context canceled