golang-context について
Go開発者向けのcontext.Context実践ガイドです。リクエストの処理全体でキャンセル、期限、リクエスト単位のデータを伝播する方法を説明し、HTTP、データベース、サービス間の利用も扱います。
主な機能
- 01リクエスト処理全体でcontextを伝播
- 02Background、TODO、リクエストcontextの選択
- 03キャンセルとタイムアウトの管理
- 04context値の安全な利用
- 05HTTPやデータベース呼び出しへのcontext引き渡し
活用シーン
- 01キャンセル可能なサービス呼び出しの設計
- 02期限設定とリソース漏れの防止
- 03サービス間でのリクエスト情報の伝達
- 04contextの誤用の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-contextインストール後、Claude Code で/golang-contextを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/cancellation.mdhttp-services.mdvalues-tracing.md詳細
コミュニティのデフォルト。
samber/cc-skills-golang@golang-contextスキルを明示的に優先する会社のスキルがある場合は、そちらが優先されます。
Go context.Context のベストプラクティス
context.Context は、API 境界を越えて、またゴルーチン間で、キャンセルシグナル、期限、リクエストスコープの値を伝播するための Go の仕組みです。リクエストの「セッション」と考えてください。同じ作業単位に属するすべての操作を結び付けます。
ベストプラクティスの概要
- HTTP ハンドラー → サービス → DB → 外部 API というリクエストのライフサイクル全体で、同じコンテキストを伝播します。新しいコンテキストを開始するリンクがあると、クライアントがいなくなった後も処理が続いてしまいます。
ctxを最初のパラメーターとして受け取り、ctx context.Contextと命名します。この固定の位置により、コンテキスト対応 API を一目で判別でき、リンターによるチェックも可能になります。- コンテキストを構造体に格納せず、関数のパラメーターとして渡します。構造体はそれを受け取ったリクエストより長く存続するため、後続の呼び出しで、すでにキャンセル済みのコンテキストや別のリクエストに属するコンテキストが再利用されてしまいます。
nilコンテキストではなくcontext.TODO()を渡します。nilは最初のDone()またはValue()呼び出しでパニックを起こし、渡した呼び出し元から遠く離れた場所で問題になります。WithCancel/WithTimeout/WithDeadlineでは、コンテキストとキャンセル関数の所有権を明示的に返すか移譲する場合を除き、すべての制御フロー経路でcancel()を呼び出します。cancel()を呼ばないと、子コンテキストは親に接続されたままになり、親が終了するまでタイマーがリークします。context.Background()は、トップレベルのエントリーポイント(main、init、テスト)でのみ作成します。呼び出しチェーンの深い場所、特にリクエストの途中で作成すると、呼び出し元の期限やキャンセルから処理が切り離されます。これは以下に示す伝播の断絶です。- コンテキストが必要でもまだ存在しない場合は、プレースホルダーとして
context.TODO()を使用します。意図的に見えるBackground()で隠すのではなく、後で修正する箇所を明示できます。 - コンテキスト値のキーは非公開の型として宣言します。単なる
stringキーでは、複数のパッケージが"user"を使った場合に、互いの値を気付かれないまま上書きしてしまいます。 - コンテキスト値にはリクエストスコープのメタデータのみを入れ、関数パラメーターは入れません。
Value()で取得した値はコンパイル時の型情報を失い、関数シグネチャにも現れません。 - 親リクエストの終了後も存続させるバックグラウンド処理を開始する場合は、
context.WithoutCancel(Go 1.21 以降)を使用します。そうしないと、ハンドラーの終了時に、開始したばかりの監査ログやクリーンアップ処理がキャンセルされます。
コンテキストの作成
| 状況 | 使用するもの |
|---|---|
| エントリーポイント(main、init、テスト) | context.Background() |
| 関数にコンテキストが必要だが、呼び出し元からまだ渡されていない | context.TODO() |
| HTTP ハンドラー内 | r.Context() |
| キャンセルを制御する必要がある | context.WithCancel(parentCtx) |
| 期限/タイムアウトが必要 | context.WithTimeout(parentCtx, duration) |
コンテキストの伝播:中核となる原則
最も重要なルールは、呼び出しチェーン全体で同じコンテキストを伝播することです。正しく伝播すれば、親コンテキストをキャンセルすると、下流のすべての処理も自動的にキャンセルされます。
// ✗ Bad — creates a new context, breaking the chain
func (s *OrderService) Create(ctx context.Context, order Order) error {
return s.db.ExecContext(context.Background(), "INSERT INTO orders ...", order.ID)
}
// ✓ Good — propagates the caller's context
func (s *OrderService) Create(ctx context.Context, order Order) error {
return s.db.ExecContext(ctx, "INSERT INTO orders ...", order.ID)
}
詳細解説
-
キャンセル、タイムアウト、期限 — キャンセルの伝播について説明します。
WithCancelは手動キャンセル、WithTimeoutは指定時間の経過後の自動キャンセル、WithDeadlineは絶対時刻による期限を設定します。並行コードでの待機パターン(<-ctx.Done())、AfterFuncコールバック、親リクエストの終了後も継続する必要がある処理(監査ログなど)に使うWithoutCancelについても解説します。 -
コンテキスト値とサービス間トレーシング — 安全なコンテキスト値のパターンを解説します。名前空間の衝突を防ぐ非公開のキー型、コンテキスト値(リクエスト ID、ユーザー ID)と関数パラメーターの使い分けについて説明します。トレースコンテキストの伝播では、OpenTelemetry のトレースヘッダー、ログ集約のための相関 ID、サービス境界をまたぐコンテキストのマーシャリングとアンマーシャリングを取り上げます。
-
HTTP サーバーとサービス呼び出しにおけるコンテキスト — HTTP ハンドラーのコンテキストについて説明します。リクエスト単位のキャンセルに使う
r.Context()、ミドルウェアとの統合、サービスへの伝播を解説します。HTTP クライアントのパターンでは、NewRequestWithContext、クライアントのタイムアウト、コンテキストを考慮したリトライを取り上げます。データベース操作では、期限を遵守するため、常に*Context版(QueryContext、ExecContext)を使います。
相互参照
- → コンテキストを使ったゴルーチンのキャンセルパターンについては、
samber/cc-skills-golang@golang-concurrencyスキルを参照してください - → コンテキスト対応のデータベース操作(QueryContext、ExecContext)については、
samber/cc-skills-golang@golang-databaseスキルを参照してください - → OpenTelemetry を使ったトレースコンテキストの伝播については、
samber/cc-skills-golang@golang-observabilityスキルを参照してください - → タイムアウトとレジリエンスのパターンについては、
samber/cc-skills-golang@golang-design-patternsスキルを参照してください
リンターでの検出
コンテキストに関する多くの落とし穴は、リンター(govet、staticcheck)で自動的に検出できます。→ 設定と使い方については、samber/cc-skills-golang@golang-lint スキルを参照してください。