返回 Skills
cxuu/golang-skills· Apache-2.0 内容可用

go-context

Use when working with context.Context in Go — placement in signatures, propagating cancellation and deadlines, and storing values in context vs parameters. Also use when cancelling long-running operations, setting timeouts, or passing request-scoped data, even if they don't mention context.Context directly. Does not cover goroutine lifecycle or sync primitives (see go-concurrency).

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: go-context description: Use when working with context.Context in Go — placement in signatures, propagating cancellation and deadlines, and storing values in context vs parameters. Also use when cancelling long-running operations, setting timeouts, or passing request-scoped data, even if they don't mention context.Context directly. Does not cover goroutine lifecycle or sync primitives (see go-concurrency).

Go Context Usage

Compatibility: context has been in the standard library since Go 1.7.

Resource Routing

  • references/PATTERNS.md - Read when deriving contexts, checking cancellation, handling HTTP request contexts, or using typed context-value keys.

Context as First Parameter

Functions that use a Context should accept it as their first parameter:

func F(ctx context.Context, /* other arguments */) error
func ProcessRequest(ctx context.Context, req *Request) (*Response, error)

This is a strong convention in Go that makes context flow visible and consistent across codebases.


Don't Store Context in Structs

Do not add a Context member to a struct type. Instead, pass ctx as a parameter to each method that needs it:

// Bad: Context stored in struct
type Worker struct {
    ctx context.Context  // Don't do this
}

// Good: Context passed to methods
type Worker struct{ /* ... */ }

func (w *Worker) Process(ctx context.Context) error {
    // Context explicitly passed — lifetime clear
}

Exception: Methods whose signature must match an interface in the standard library or a third-party library may need to work around this.


Don't Create Custom Context Types

Do not create custom Context types or use interfaces other than context.Context in function signatures:

// Bad: Custom context type
type MyContext interface {
    context.Context
    GetUserID() string
}

// Good: Use standard context.Context with value extraction
func Process(ctx context.Context) error {
    userID := GetUserID(ctx)
}

Where to Put Application Data

Consider these options in order of preference:

  1. Function parameters — most explicit and type-safe
  2. Receiver — for data that belongs to the type
  3. Globals — for truly global configuration (use sparingly)
  4. Context value — only for request-scoped data

Context values are appropriate for:

  • Request IDs and trace IDs
  • Authentication/authorization info that flows with requests
  • Deadlines and cancellation signals

Context values are not appropriate for:

  • Optional function parameters
  • Data that could be passed explicitly
  • Configuration that doesn't vary per-request

Common Patterns

Deriving Contexts

Always defer cancel() immediately after creating a derived context:

ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

Checking Cancellation

select {
case <-ctx.Done():
    return ctx.Err()
default:
    // Do work
}

Context Immutability

Contexts are immutable — it's safe to pass the same ctx to multiple concurrent calls that share the same deadline and cancellation signal.


Related Skills

  • Goroutine coordination: See go-concurrency when using context for goroutine cancellation, select-based timeouts, or errgroup
  • Error handling: See go-error-handling when deciding how to wrap or return ctx.Err() cancellation errors
  • Interface design: See go-interfaces when designing APIs that accept context alongside interfaces
  • Request-scoped logging: See go-logging when injecting loggers into context or adding request IDs to structured log output

附带文件

references/PATTERNS.md
# Context Patterns

Common patterns for deriving, checking, and propagating `context.Context`.

## Contents

- [Context Immutability](#context-immutability)
- [When to Use context.Background()](#when-to-use-contextbackground)
- [Deriving Contexts](#deriving-contexts)
- [Checking Cancellation](#checking-cancellation)
- [Respecting Cancellation in HTTP Handlers](#respecting-cancellation-in-http-handlers)
- [Context Value Best Practices](#context-value-best-practices)
- [Quick Reference](#quick-reference)

## Context Immutability

Contexts are immutable. It's safe to pass the same `ctx` to multiple calls that
share the same deadline, cancellation signal, credentials, and parent trace:

```go
// Safe: same context to sequential calls
func ProcessBatch(ctx context.Context, items []Item) error {
    for _, item := range items {
        if err := process(ctx, item); err != nil {
            return err
        }
    }
    return nil
}

// Safe: same context to concurrent calls
func ProcessConcurrently(ctx context.Context, a, b *Data) error {
    g, ctx := errgroup.WithContext(ctx)
    g.Go(func() error { return processA(ctx, a) })
    g.Go(func() error { return processB(ctx, b) })
    return g.Wait()
}
```

---

## When to Use context.Background()

Use `context.Background()` only for functions that are **never request-specific**:

```go
func main() {
    ctx := context.Background()
    if err := run(ctx); err != nil {
        log.Fatal(err)
    }
}

func startBackgroundWorker() {
    ctx := context.Background()
    go worker(ctx)
}
```

**Default to passing a Context** even if you think you don't need to. Only use
`context.Background()` directly if you have a good reason why passing a context
would be a mistake:

```go
func LoadConfig(ctx context.Context) (*Config, error) {
    // Even if not using ctx now, accepting it allows future
    // additions without API changes
}
```

---

## Deriving Contexts

```go
// Add timeout — cancel fires after duration elapses
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

// Add cancellation — caller controls when to cancel
ctx, cancel := context.WithCancel(ctx)
defer cancel()

// Add deadline — cancel fires at a specific wall-clock time
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Hour))
defer cancel()

// Add value (use sparingly — only for request-scoped data)
ctx = context.WithValue(ctx, requestIDKey, reqID)
```

**Always `defer cancel()`** immediately after creating a derived context. This
ensures resources are released even if the function returns early.

### Nested Derivation

Derived contexts form a tree. Cancelling a parent cancels all its children:

```go
func handleRequest(ctx context.Context) error {
    // Parent timeout for the whole request
    ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
    defer cancel()

    // Tighter timeout for the database call
    dbCtx, dbCancel := context.WithTimeout(ctx, 5*time.Second)
    defer dbCancel()

    data, err := queryDB(dbCtx)
    if err != nil {
        return err
    }

    // Remaining time from parent context applies here
    return sendResponse(ctx, data)
}
```

---

## Checking Cancellation

### In Long-Running Loops

```go
func LongRunningOperation(ctx context.Context) error {
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            // Do work
        }
    }
}
```

### Before Expensive Operations

Check cancellation before starting work that can't be interrupted:

```go
func ProcessItems(ctx context.Context, items []Item) error {
    for _, item := range items {
        if ctx.Err() != nil {
            return ctx.Err()
        }
        if err := expensiveProcess(item); err != nil {
            return err
        }
    }
    return nil
}
```

### Distinguishing Cancellation Causes

```go
if err := ctx.Err(); err != nil {
    switch {
    case errors.Is(err, context.Canceled):
        // Caller explicitly cancelled (e.g., client disconnected)
    case errors.Is(err, context.DeadlineExceeded):
        // Timeout or deadline passed
    }
}
```

---

## Respecting Cancellation in HTTP Handlers

```go
func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()

    result, err := slowOperation(ctx)
    if err != nil {
        if errors.Is(err, context.Canceled) {
            // Client disconnected — nothing to write
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    json.NewEncoder(w).Encode(result)
}
```

The `r.Context()` is cancelled when:
- The client closes the connection
- The request is cancelled by the client or HTTP/2 transport
- The `ServeHTTP` method returns

---

## Context Value Best Practices

### Use Unexported Key Types

```go
type contextKey struct{}

var userIDKey contextKey

func WithUserID(ctx context.Context, id string) context.Context {
    return context.WithValue(ctx, userIDKey, id)
}

func UserIDFromContext(ctx context.Context) (string, bool) {
    id, ok := ctx.Value(userIDKey).(string)
    return id, ok
}
```

Using an unexported struct type as the key prevents collisions with keys from
other packages — even if they use the same string or int value.

### Provide Accessor Functions

Always wrap `context.WithValue` and `ctx.Value` in typed helper functions (as
shown above) rather than exposing keys. This gives you type safety and a single
place to change the implementation.

---

## Quick Reference

| Pattern | Guidance |
|---------|----------|
| Parameter position | Always first: `func F(ctx context.Context, ...)` |
| Struct storage | Don't store in structs; pass to methods |
| Custom types | Don't create; use `context.Context` interface |
| Application data | Prefer parameters > receiver > globals > context values |
| Request-scoped data | Appropriate for context values |
| Sharing context | Safe — contexts are immutable |
| `context.Background()` | Only for non-request-specific code |
| Default | Pass context even if you think you don't need it |
| `defer cancel()` | Always defer immediately after `WithTimeout`/`WithCancel`/`WithDeadline` |
| Value keys | Use unexported struct types, provide accessor functions |
| Cancellation check | `ctx.Err()` before expensive ops; `select` on `ctx.Done()` in loops |
    go-context | Prompt Minder