// lesson: context-cancellation

Context โ€” Cancellation, Deadlines, Propagation

Go gives you no way to kill a goroutine. That is deliberate: forcibly stopping a thread mid-flight leaves locks held and invariants broken, so cancellation in Go is cooperative โ€” you ask, the goroutine complies. context.Context is the standard way to ask.

A context carries three things across API boundaries: a cancellation signal, an optional deadline, and request-scoped values. The signal is a channel, closed exactly once:

ctx, cancel := context.WithTimeout(parent, 2*time.Second)
defer cancel() // always: stops the timer and detaches from the parent

select {
case res := <-work:
	return res, nil
case <-ctx.Done():
	return nil, ctx.Err() // context.Canceled or context.DeadlineExceeded
}

Contexts form a tree. WithCancel, WithTimeout, and WithDeadline derive a child from a parent; canceling a parent cancels every descendant, while canceling a child never affects the parent. This is what makes the abstraction compose: an HTTP handler's context is canceled when the client disconnects, and every database call, RPC, and worker goroutine spawned under it unwinds automatically โ€” provided each one actually selects on ctx.Done() while blocking.

Two rules keep this honest. First, ctx is always the first parameter, never stored in a struct โ€” it belongs to a call chain, not an object. Second, if you create a context, you defer cancel() even on success: until it is canceled (or its deadline fires), a child stays registered with its parent, keeping its timer and bookkeeping alive longer than needed โ€” and anything blocked on its Done channel waits with it.

The pattern you will implement below โ€” race several attempts, keep the first success, cancel the rest โ€” is the canonical use of a locally derived cancel: winners cancel losers, without touching the caller's context.

โ€บ First Result Wins

30 pts

Implement:

func FirstResult(ctx context.Context, fns []func(ctx context.Context) (string, error)) (string, error)

Semantics:

  • Run every fn concurrently, each receiving a context derived from ctx.
  • The first fn to return a nil error wins: return its string and cancel the context passed to the remaining fns, promptly.
  • If every fn fails, return a non-nil error (any of the failures is acceptable).
  • If ctx is canceled before any success, return promptly with a non-nil error.
  • If fns is empty, return a non-nil error.

FirstResult must not wait for the losers to finish, but the losers must observe cancellation through their context.

Log in to submit a solution and earn points.