Context Control in Go

Go developers weigh how to use `context` and goroutines responsibly, focusing on cancellation, resource lifetimes, and avoiding hidden concurrency in libraries. Many favor synchronous-looking APIs where callers control whether work runs concurrently, while acknowledging legitimate cases for internal goroutines and long‑running workers. The conversation also critiques `context` as a mixed bag of cancellation and request-scoped data, compares it to patterns in Rust and C#, and touches on structured concurrency and the limits of Go’s “no async coloring” ideal.

Goroutines in Libraries & API Design

  • Strong theme: library APIs should appear synchronous; callers decide if they want concurrency by spawning goroutines themselves.
  • Internal goroutines are acceptable when they are:
    • Clearly part of the abstraction (e.g., crawler, parallel map, batching pub/sub, metrics flusher).
    • Bounded, well-documented, and tunable (limits, rate, batching, in‑flight caps).
    • Properly cleaned up before the public function returns (structured concurrency style, often via errgroup or WaitGroup).
  • Critics of “never start goroutines in libraries” call that overly rigid; some workloads are simpler and safer if the library owns scheduling.
  • Panics in background goroutines are a concern: callers can’t wrap those in recover unless they own the goroutine boundary.

Context: Purpose, Pattern, and Pain Points

  • Two main roles:
    • Cancellation / deadlines / “you can stop now”.
    • Request-scoped data (logger, tracing, auth, etc.).
  • Many dislike the “bag of stuff” aspect; it’s hard to know why a function needs a context or what’s inside.
  • Others defend explicit ctx parameters:
    • Make interruptibility and logging requirements visible.
    • Easier to review and reason about than implicit thread-local storage.
  • Guidance debated:
    • Common rule: don’t store contexts; pass them down the stack.
    • Nuance: ok in structs that are effectively parameters, and in stdlib for backward compatibility (e.g., net/http), sometimes using NewRequestWithContext.
    • context.WithoutCancel is seen as a useful compromise when storing “values but not deadlines”.
  • Friction points:
    • No way to statically know if callees honor context.
    • Pressure to thread ctx through “almost every function”, especially for logging.

Context vs Function Coloring / Async Models

  • Some see ctx as a mild “coloring” (functions with vs without context), partially undermining Go’s “no async keyword” story.
  • Others argue it’s far less constraining than async/await:
    • You can always use context.Background() or wrap it with timeouts.
    • It doesn’t create the hard sync/async separation seen in other languages.

Error Handling & Zero Values (Side Thread)

  • Debate over the (T, error) pattern:
    • One camp: zero/nil values should be “useful” even when error is set.
    • Another: in most real APIs, zero values are meaningless or dangerous; lack of sum types forces awkward conventions.