mcpField
G
go-context

go-context

github.com/eduardo-sl開発86180 回インストール3 週間前に更新
リポジトリ

go-context について

go-contextは、キャンセル、期限、リクエスト単位の値を扱うGo開発者向けのスキルです。API、HTTPハンドラー、テストでContextを安全に伝播する規則と資料を提供します。

主な機能

  1. 01Context引数の規則
  2. 02キャンセルとタイムアウトの処理
  3. 03リクエスト単位の値の指針
  4. 04HTTPとテストのパターン
  5. 05キャンセル原因と切り離した処理

活用シーン

  1. 01Go APIのContext利用のレビュー
  2. 02goroutineやタイマーのリーク防止
  3. 03キャンセルとタイムアウト動作のテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-context

インストール後、Claude Code で/go-contextを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
cause-and-detach.md
http-and-testing.md
timeout-budgets.md
values.md

詳細

Go Context

context.Contextは、API 境界を越えてキャンセル、期限、リクエストスコープの値を制御します。誤った使い方をすると、ゴルーチンのリーク、孤立した処理、本番環境での微妙なバグを引き起こします。

コミュニティのデフォルトです。go-contextを明示的に置き換えるプロジェクトのスキルやエージェント設定ルールがある場合は、そちらが優先されます。

必要に応じて読み込む詳細なリファレンス資料:

  • references/timeout-budgets.md — 親の残り時間の割り当て、ctx.Deadline()の読み取り。
  • references/values.md — コンテキストキーの型、アクセサー、衝突の落とし穴。
  • references/http-and-testing.md — ハンドラーやミドルウェアでのリクエストコンテキスト、キャンセルのテスト。
  • references/cause-and-detach.md — WithCancelCause、WithTimeoutCause、WithoutCancel、AfterFunc。

以下のセクションだけでは不十分な場合に限り、リファレンスファイルを読んでください。

1. 基本ルール

Contextは常に最初のパラメーターにします:
// ✅ Good — context is first
func GetUser(ctx context.Context, id string) (*User, error)
func (s *Service) Process(ctx context.Context, req Request) error

// ❌ Bad — context buried in the middle or end
func GetUser(id string, ctx context.Context) (*User, error)
func Process(req Request, ctx context.Context) error
Contextを構造体に格納してはいけません:
// ❌ Bad — context stored in struct
type Server struct {
    ctx    context.Context // NEVER do this
    cancel context.CancelFunc
}

// ✅ Good — pass context through method parameters
func (s *Server) Shutdown(ctx context.Context) error {
    return s.httpServer.Shutdown(ctx)
}

Contextが表すのは単一の操作のライフタイムであり、オブジェクトのライフタイムではありません。

nilのContextを渡してはいけません:
// ❌ Bad
doSomething(nil, data)

// ✅ Good — use context.TODO() if unsure which context to use
doSomething(context.TODO(), data)

// ✅ Good — use context.Background() for top-level/main
doSomething(context.Background(), data)

2. キャンセル

必ずcancelをdeferします:
// ✅ Good — cancel called even if operation succeeds
ctx, cancel := context.WithCancel(parentCtx)
defer cancel()

result, err := longOperation(ctx)

cancelを呼び出さないと、親のContextがキャンセルされるまでリソース(タイマー、ゴルーチン)がリークします。

手動キャンセルにはWithCancelを使います:
func (s *Supervisor) Run(ctx context.Context) error {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    g, ctx := errgroup.WithContext(ctx)

    g.Go(func() error { return s.runWorkerA(ctx) })
    g.Go(func() error { return s.runWorkerB(ctx) })

    // If any worker returns an error, errgroup cancels ctx,
    // which signals all other workers to stop.
    return g.Wait()
}
ループ内でContextのキャンセルを確認します:
// ✅ Good — respects cancellation
func processItems(ctx context.Context, items []Item) error {
    for _, item := range items {
        if err := ctx.Err(); err != nil {
            return fmt.Errorf("processing cancelled: %w", err)
        }
        if err := process(ctx, item); err != nil {
            return fmt.Errorf("process item %s: %w", item.ID, err)
        }
    }
    return nil
}

// ❌ Bad — runs to completion even if cancelled
func processItems(ctx context.Context, items []Item) error {
    for _, item := range items {
        process(ctx, item) // ignores ctx cancellation between items
    }
    return nil
}

3. タイムアウトと期限

期間ベースの制限にはWithTimeoutを使います:
func (c *Client) FetchUser(ctx context.Context, id string) (*User, error) {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url+"/users/"+id, nil)
    if err != nil {
        return nil, fmt.Errorf("create request: %w", err)
    }

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("fetch user %s: %w", id, err)
    }
    defer resp.Body.Close()

    // ...
}
絶対時刻による制限にはWithDeadlineを使います:
// Use when coordinating with external deadlines (SLAs, cron windows)
deadline := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
ctx, cancel := context.WithDeadline(ctx, deadline)
defer cancel()
タイムアウト予算

子のタイムアウトは親の予算の一部を消費し、延長することはありません。30秒のリクエストハンドラー内にある5秒のDB呼び出しと10秒のAPI呼び出しは、予算の範囲内です。残り5秒の親の下にある60秒の子は、親の期限でひそかに発火するため、深夜3時にはバグのように見えます。

中断できない処理を始める前に、ctx.Deadline()で残り時間を確認し、短すぎる場合はすぐに失敗させてください。

例はreferences/timeout-budgets.mdを参照してください。

4. Contextの値

リクエストスコープのメタデータのみを格納してください。リクエストID、トレースIDまたはスパンID、認証済みユーザー、リクエストスコープのロガーなどです。データベース接続、設定、関数の明示的なパラメーターとして渡せるものは格納しないでください。

キーには非公開の型を使い、文字列は使わないでください。文字列キーはプロセス内の別のパッケージによって上書きされる可能性があります。アクセサー関数を公開し、呼び出し側がctx.Valueの型アサーションを直接行わなくて済むようにしてください。

例はreferences/values.mdを参照してください。

5. HTTPハンドラーとテストでのContext

ハンドラーではr.Context()を受け取り、下流へ渡します。リクエストがキャンセルされた場合はクライアントが切断されたことを意味するため、応答を書き込まずに戻ります。ミドルウェアではr.WithContext(ctx)を使って値を付加します。

テストではcontext.Background()ではなく、t.Context()(Go 1.24以降)からContextを派生させます。テスト対象の呼び出しをcontext.WithTimeoutで囲み、ハングした場合にスイートをブロックするのではなくテストが失敗するようにします。また、キャンセルが正しく処理されることを確認するため、errors.Is(err, context.Canceled)をアサートします。

例はreferences/http-and-testing.mdを参照してください。

6. 原因、切り離した処理、AfterFunc

必要なことAPI(Goのバージョン)
処理がキャンセルされた理由を記録するWithCancelCause + context.Cause(1.20)
どのタイムアウトが発生したかを示すWithTimeoutCause、WithDeadlineCause(1.21)
リクエストの戻り後も、値を保持して処理を完了するWithoutCancel + 独自のタイムアウト(1.21)
Contextを受け取らない呼び出しのブロックを解除するAfterFunc(1.21)

切り離した処理には、必ず独自のタイムアウトを設定してください。context.Background()もリクエストより長く存続しますが、トレースIDは引き継ぎません。例はreferences/cause-and-detach.mdを参照してください。

7. context.Background()とcontext.TODO()の使い分け

関数使用する場面
context.Background()最上位:main()、init()。意図的なルートContextです。テストではt.Context()(Go 1.24以降)を使います。
context.TODO()どのContextを使うべきかまだ分からない場合のプレースホルダーです。「修正が必要」であることを示します。

context.TODO()は本番コードではコードの臭いです。リリース前に置き換えてください。

検証チェックリスト

  1. context.Context を受け取るすべての関数で、最初のパラメーターが context.Context になっていること
  2. 構造体のフィールドにコンテキストを保存していないこと
  3. WithCancel、WithTimeout、WithDeadline の直後に defer cancel() を呼び出していること
  4. 長いループでは、反復処理の間に ctx.Err() を確認していること
  5. 子コンテキストのタイムアウトが、親コンテキストのタイムアウトの残り時間を超えていないこと
  6. コンテキスト値では、アクセサー関数と非公開のキー型を使用していること
  7. コンテキスト値には、リクエスト単位のメタデータのみを保存していること(設定や接続は保存しない)
  8. HTTP ハンドラーでは r.Context() を使用し、後続処理に渡していること
  9. nil コンテキストを渡していないこと — context.TODO() または context.Background() を使用すること
  10. テストでは、ハングを防ぐために context.WithTimeout を使用していること
  11. リクエスト終了後も続行する処理では、context.Background() ではなく、WithoutCancel と独自のタイムアウトを使用していること

コメント