メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#79040likely_accept

net/http: drop support for Transport.CancelRequest

ステータス変更: active likely_accept

要約

AIによる要約であり、誤りを含む場合があります。

概要

net/httpパッケージのTransport.CancelRequestメソッドについて、実質的な機能を廃止しno-op(何もしない関数)にすることを提案するproposalです。10年前から非推奨とされてきたこの古いリクエストキャンセル機構を、コンテキストベースのNewRequestWithContextに完全に置き換えます。

ステータス変更

activelikely_accept
2026年7月1日と7月8日の2回にわたるProposal Reviewミーティングでの議論を経て、明確な反対意見が出なかったため「likely accept(承認見込み)」となりました。特に7月8日の会議では、オプトイン期間を設けるべきかという論点についてneild氏が「実際にオプトインする利用者はほぼいないだろう」と指摘し、段階を踏まずに直接変更を進める方針で合意しました。

技術的背景

現状の問題点

Transport.CancelRequestはGo 1.6(2016年2月)で非推奨となり、Go 1.13で導入されたNewRequestWithContextによってさらに時代遅れの機構になっています。ドキュメント上も「HTTP/2リクエストはキャンセルできない」「将来のリリースでno-opになる可能性がある」と明記されています。
問題は、このメソッドをサポートし続けること自体のコストです。CancelRequestを実現するには、進行中の全リクエストをマップで管理し続ける必要があります。リクエストへ直接キャンセル状態を持たせる実装にできない理由は、ユーザーが進行中の*RequestnewReq := *reqのようにコピーする慣習があり、RoundTripが入力リクエストを変更するとレースディテクタがエラーを検出してしまうためです。つまり、ほぼ誰も使っていない機能のために、すべてのHTTPリクエストがコスト(マップ管理のオーバーヘッド)を負担している状態です。

提案された解決策

Transport.CancelRequestを以下のようにno-op化し、初回呼び出し時に一度だけ警告ログを出力する実装が提案されています。

var cancelRequestOnce sync.Once
func (*Transport) CancelRequest(req *Request) {
  cancelRequestOnce.Do(func() {
    log.Println("Use of unsupported Transport.CancelRequest. Use NewRequestWithContext instead.")
  })
}

さらに、GODEBUG=httptransportcancelrequest=1という環境変数で旧動作を再度有効化できるようにし、これは「GODEBUGフラグ削除ポリシー」proposal(#76163、本会議で承認)で定められた標準ライフサイクルである4リリースサイクルの間維持された後、完全に削除される予定です。

これによって何ができるようになるか

このproposal自体は新機能を追加するものではなく、既存の非推奨機能を整理してコード品質と性能を改善するものです。恩恵を受けるのは主にGo標準ライブラリの内部実装とすべてのnet/http利用者です。

  • HTTP/1リクエスト処理の高速化・簡素化: 進行中リクエストのマップ管理という「実質誰も使わない機能のためのコスト」を取り除けるため、Transportの内部実装がシンプルになります。
  • HTTP/2との一貫性: CancelRequestはもともとHTTP/2リクエストをキャンセルできない仕様であり、no-op化により全プロトコルで一貫した(キャンセルされない)挙動になります。
  • 移行漏れの検出: GODEBUGとログ出力により、CancelRequestに依存したままのコードを開発者が発見しやすくなります。

コード例

// Before: 非推奨だが動作する古いキャンセル方法
tr := &http.Transport{}
req, _ := http.NewRequest("GET", url, nil)
resp, err := tr.RoundTrip(req)
// ...後で中断したくなった場合
tr.CancelRequest(req) // 実際にキャンセルされる(ただしHTTP/1のみ)
// After: contextベースの標準的なキャンセル方法
ctx, cancel := context.WithCancel(context.Background())
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := tr.RoundTrip(req)
// ...後で中断したくなった場合
cancel() // HTTP/1・HTTP/2いずれも確実にキャンセルされる

議論のハイライト

  • ログ出力の是非: mateusz834氏やapparentlymart氏から「no-op化自体は問題ないが、意図しないログ出力がCLIツールなど出力形式に依存するプログラムを壊す可能性がある」と懸念が示されました。これに対しneild氏は「サイレントに失敗するより、ログで気づける方が安全」と反論し、7月1日の会議でコアチームは「ログについて強いこだわりはなく、削っても構わない」との見解を示しています。
  • オプトイン移行期間の要否: 7月8日の会議でneild氏は「オプトイン方式にしても、実際に有効化するユーザーはほとんどいないだろう」と述べ、段階的移行の価値に疑問を呈しました。結果として、GODEBUGによるオプトアウト(旧動作への回帰)のみを用意し、事前のオプトインステップは設けない方針となりました。
  • 「賢い」代替案の却下: CancelRequestが呼ばれる可能性があるかどうかを静的に判定する「オラクル」を作る技術的な可能性についても議論されましたが、「too clever(凝りすぎ)」であり、永続的にメンテナンスコストを負うことになるとして却下されました。
  • GODEBUGライフサイクルとの連携: 本proposalは同日承認された「GODEBUGフラグ削除ポリシー」(#76163)に沿った4リリースサイクルの猶予期間を採用しており、Go全体のGODEBUG運用方針の実例として位置づけられています。
  • 既存の前例: aclements氏は「net以下には既にログを出す箇所がいくつかあり、それ自体は望ましくないかもしれないが前例がある」と述べ、今回のログ出力を正当化しました。

関連リンク