noctx

Table of Contents

  1. 解決する問題
  2. 設定
  3. オプション
  4. サンプル
    1. 検出例
    2. 修正例
  5. 注意点
  6. 参考リンク

noctx は context.Context を渡さずに呼び出している関数を検出するツールです。

解決する問題

http.Get などの context を受け取らない関数を使うと、リクエストのキャンセルやタイムアウト制御ができません。長時間応答しない外部 API に対してリクエストが滞留し、goroutine リークやリソース枯渇を引き起こします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Before: context なしの HTTP リクエスト
func FetchUser(url string) (*User, error) {
// キャンセル・タイムアウト不可
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("fetch user: %w", err)
}
defer resp.Body.Close()

var user User
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, fmt.Errorf("decode user: %w", err)
}
return &user, nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// After: context を使った HTTP リクエスト
func FetchUser(ctx context.Context, url string) (*User, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch user: %w", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
slog.Error("close response body", "error", err)
}
}()

var user User
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, fmt.Errorf("decode user: %w", err)
}
return &user, nil
}

設定

公式ドキュメント

設定オプションはありません。有効化するだけで動作します。

1
2
3
4
5
6
# .golangci.yml
version: "2"

linters:
enable:
- noctx

オプション

golangci-lint 経由では設定オプションはありません。

サンプル

検出例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// noctx が警告するパターン

func example() {
// net/http: context なしのリクエスト
resp, _ := http.Get("https://example.com") // net/http.Get must not be called
resp, _ = http.Head("https://example.com") // net/http.Head must not be called
resp, _ = http.Post("https://example.com", "", nil) // net/http.Post must not be called
resp, _ = http.PostForm("https://example.com", nil) // net/http.PostForm must not be called
_ = resp

// context なしの http.NewRequest
req, _ := http.NewRequest("GET", "https://example.com", nil) // should use NewRequestWithContext
_ = req
}

修正例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func example(ctx context.Context) error {
// http.NewRequestWithContext を使用
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", nil)
if err != nil {
return fmt.Errorf("create request: %w", err)
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
slog.Error("close response body", "error", err)
}
}()

return nil
}

注意点

  • http.Get / http.Post などの便利関数は常に http.NewRequestWithContext + Client.Do に置き換える
  • http.NewRequest も検出対象。http.NewRequestWithContext を使う
  • noctx は net/http 以外にも database/sqlnetos/exec パッケージの context なし呼び出しも検出する

参考リンク