fatcontext はループや関数リテラル内で context が肥大化するパターンを検出するツールです。
解決する問題
ループ内で ctx = context.WithValue(ctx, ...) のように context を再代入すると、ループのたびに前の context をラップした新しい context が作られ、チェーンが際限なく伸びていきます。これにより context が「肥大化(fat)」し、メモリ使用量の増大と値の検索パフォーマンス低下を引き起こします。
1 2 3 4 5 6 7 8 9 10
| func ProcessItems(ctx context.Context, items []Item) error { for _, item := range items { ctx = context.WithValue(ctx, "item_id", item.ID) if err := process(ctx, item); err != nil { return fmt.Errorf("process item %s: %w", item.ID, err) } } return nil }
|
1 2 3 4 5 6 7 8 9 10
| func ProcessItems(ctx context.Context, items []Item) error { for _, item := range items { ctx := context.WithValue(ctx, "item_id", item.ID) if err := process(ctx, item); err != nil { return fmt.Errorf("process item %s: %w", item.ID, err) } } return nil }
|
設定
公式ドキュメント
1 2 3 4 5 6 7 8 9
| version: "2"
linters: enable: - fatcontext settings: fatcontext: check-struct-pointers: false
|
オプション
| オプション |
型 |
デフォルト |
説明 |
check-struct-pointers |
bool |
false |
構造体のポインタフィールドの context もチェック対象にする。誤検知が増える可能性がある |
サンプル
検出例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
func handleBatch(ctx context.Context, ids []string) { for _, id := range ids { ctx = context.WithValue(ctx, "id", id) doWork(ctx) } }
func pollItems(ctx context.Context, items []Item) { for _, item := range items { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) process(ctx, item) cancel() } }
func setup(ctx context.Context) { fn := func() { ctx = context.WithValue(ctx, "key", "value") use(ctx) } fn() }
|
修正例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func handleBatch(ctx context.Context, ids []string) { for _, id := range ids { ctx := context.WithValue(ctx, "id", id) doWork(ctx) } }
func pollItems(ctx context.Context, items []Item) { for _, item := range items { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) process(ctx, item) cancel() } }
func setup(ctx context.Context) { fn := func() { ctx := context.WithValue(ctx, "key", "value") use(ctx) } fn() }
|
注意点
=(代入)と :=(宣言)の違いが重要。ループ内では := を使ってシャドーイングすることで、各イテレーションで独立した context を作成できる
context.WithValue、context.WithCancel、context.WithTimeout、context.WithDeadline のすべてが対象
check-struct-pointers: true にすると、構造体フィールドに保持された context のチェックも行うが、誤検知が増える可能性がある
- context の肥大化はメモリリークに似た症状を引き起こすが、GC では回収されないため、長時間稼働するサーバーで特に問題になる
参考リンク