A select can branch between a canceled context and a default path.

done channel `ctx.Done()` is a channel that becomes ready after cancellation.

Cancellation Branch

cancelNow
cancellation_branch.go
Replay: real traced execution (multi-file project)
package main

import (
	"context"
	"fmt"
)

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

	if cancelNow {
		cancel()
	}

	status := ""
	select {
	case <-ctx.Done():
		status = "canceled"
	default:
		status = "still running"
	}

	fmt.Println("cancelNow=", cancelNow)
	fmt.Println("status=", status)
	fmt.Println("hasError=", ctx.Err() != nil)
}
package main

import (
	"context"
	"fmt"
)

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

	if cancelNow {
		cancel()
	}

	status := ""
	select {
	case <-ctx.Done():
		status = "canceled"
	default:
		status = "still running"
	}

	fmt.Println("cancelNow=", cancelNow)
	fmt.Println("status=", status)
	fmt.Println("hasError=", ctx.Err() != nil)
}
  1. cancelNow ← true, 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 cancelNow→ true = true //@cancelNow=false10  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())
  2. if cancelNow

    12if cancelNowtrue {13  cancel()14}
  3. status ← ""

    16  status→ "" := ""17  select {18  case <-ctx.Done():19    status→ "canceled" = "canceled"20  default:21    status = "still running"22  }2324  fmt.Println("cancelNow=", cancelNowtrue)25  fmt.Println("status=", status"canceled")26  fmt.Println("hasError=", 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)27}
    outputcancelNow= true
    status= canceled
    hasError= true
  1. cancelNow ← false, 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 cancelNow→ false = false10  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())1112  if cancelNow {13    cancel()14  }1516  status→ "" := ""17  select {18  case <-ctx.Done():19    status = "canceled"20  default:21    status = "still running"22  }2324  fmt.Println("cancelNow=", cancelNowfalse)25  fmt.Println("status=", status"still running")26  fmt.Println("hasError=", 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:error(nil), cause:error(nil)}.Err() != nil)27}
    outputcancelNow= false
    status= still running
    hasError= false