Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18507] ファイルの概要

このコミットは、Go言語の標準ライブラリである net/http パッケージにおいて、パニック発生時のスタックトレースバッファサイズを4KBから64KBに拡張する変更です。これにより、特にテンプレート実行時など、スタックトレースが長くなる状況での情報欠落を防ぎ、デバッグの助けとなる完全なトレースが出力されるようになります。

コミット

commit 645a341b7d4210eece285c8dbe6e3e6cdbfbe35e
Author: David Symonds <dsymonds@golang.org>
Date:   Fri Feb 14 10:15:38 2014 +1100

    net/http: increase panic stack trace buffer size from 4 KB to 64 KB.
    
    4 KB is a bit too small in some situations (e.g. panic during a
    template execution), and ends up with an unhelpfully-truncated trace.
    64 KB should be much more likely to capture the useful information.
    There's not a garbage generation issue, since this code should only
    be triggered when there's something seriously wrong with the program.
    
    LGTM=bradfitz
    R=bradfitz
    CC=golang-codereviews
    https://golang.org/cl/63520043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/645a341b7d4210eece285c8dbe6e3e6cdbfbe35e

元コミット内容

net/http パッケージにおけるパニック時のスタックトレースバッファサイズを、従来の4KBから64KBへと増大させる変更。これにより、特にテンプレートの実行中など、スタックトレースが長大になるケースで、トレースが途中で切れてしまう問題を解消し、より有用なデバッグ情報を提供できるようになる。この変更は、プログラムに深刻な問題が発生した場合にのみトリガーされるため、ガベージ生成に関する懸念はないとされている。

変更の背景

Go言語の net/http パッケージは、HTTPサーバーの実装を提供します。このサーバーがリクエストを処理している最中にパニック(Goにおけるランタイムエラーの一種)が発生した場合、通常は recover 機構によって捕捉され、エラー情報と共にスタックトレースがログに出力されます。

しかし、従来の net/http パッケージでは、このスタックトレースを格納するためのバッファサイズが4KBに固定されていました。コミットメッセージに記載されているように、特にWebアプリケーションでテンプレートのレンダリング中にパニックが発生するような複雑なシナリオでは、コールスタックが深くなり、生成されるスタックトレースのテキストが4KBを容易に超えてしまうことがありました。

スタックトレースがバッファサイズを超過すると、トレースの末尾が切り捨てられてしまい、問題の根本原因を特定するために必要な情報が欠落する可能性がありました。これは、開発者にとってデバッグ作業を著しく困難にする問題でした。このコミットは、この「不親切に切り詰められたトレース」という問題を解決し、より完全なデバッグ情報を提供することを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と net/http パッケージの動作に関する知識が必要です。

  1. PanicとRecover:

    • Panic: Go言語におけるパニックは、プログラムの通常の実行フローを中断させるランタイムエラーです。これは、配列の範囲外アクセス、nilポインタのデリファレンス、または明示的な panic 関数の呼び出しなどによって発生します。パニックが発生すると、現在のゴルーチンは実行を停止し、遅延関数(defer)が実行されながらコールスタックを巻き戻していきます。
    • Recover: recover は、defer 関数内で呼び出された場合にのみ機能する組み込み関数です。recover がパニック中のゴルーチンで呼び出されると、パニックの値を捕捉し、ゴルーチンの実行を再開させることができます。これにより、プログラムがクラッシュするのを防ぎ、エラーを適切に処理する機会が与えられます。net/http サーバーでは、リクエスト処理中に発生したパニックを捕捉し、サーバー全体が停止するのを防ぐために recover が利用されています。
  2. スタックトレース (Stack Trace):

    • スタックトレースは、プログラムが特定の時点(通常はエラーやパニックが発生した時点)で実行していた関数の呼び出し履歴を示すリストです。各エントリは、関数名、ファイル名、行番号を含み、問題が発生した場所とその呼び出し元を特定するのに役立ちます。Goでは、runtime.Stack 関数を使用して現在のゴルーチンのスタックトレースを取得できます。
  3. net/http パッケージ:

    • Goの標準ライブラリの一部であり、HTTPクライアントとサーバーの実装を提供します。HTTPサーバーは、受信したリクエストを処理するためにゴルーチンを起動します。このコミットで変更される (*conn).serve() メソッドは、個々のHTTP接続を処理するゴルーチン内で実行されます。
  4. runtime.Stack 関数:

    • runtime.Stack(buf []byte, all bool) int は、現在のゴルーチンのスタックトレースを buf に書き込み、書き込まれたバイト数を返します。alltrue の場合、すべてのゴルーチンのスタックトレースが書き込まれますが、このコミットでは false が使用されており、現在のゴルーチンのトレースのみが対象です。buf が小さすぎる場合、トレースは切り詰められます。

技術的詳細

このコミットの技術的な核心は、net/http パッケージの (*conn).serve() メソッド内の defer 関数ブロックにあります。この defer 関数は、HTTPリクエストの処理中に発生したパニックを捕捉し、そのスタックトレースをログに出力する役割を担っています。

変更前は、スタックトレースを格納するためのバッファ buf のサイズが const size = 4096 (4KB) とハードコードされていました。

// 変更前
func (c *conn) serve() {
	defer func() {
		if err := recover(); err != nil {
			const size = 4096 // 4 KB
			buf := make([]byte, size)
			buf = buf[:runtime.Stack(buf, false)]
			log.Printf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
		}
	}()
	// ... リクエスト処理ロジック ...
}

この4KBというサイズは、多くの一般的なパニックケースでは十分でしたが、コミットメッセージが指摘するように、特にWebアプリケーションで複雑なテンプレートエンジンを使用している場合など、コールスタックが非常に深くなる状況では、生成されるスタックトレースのテキストが4KBを容易に超えてしまうことがありました。runtime.Stack 関数は、提供されたバッファに収まらない場合、トレースを切り詰めてしまうため、デバッグに必要な情報が失われる結果となっていました。

このコミットでは、size 定数が 64 << 10 に変更されました。

// 変更後
func (c *conn) serve() {
	defer func() {
		if err := recover(); err != nil {
			const size = 64 << 10 // 64 KB
			buf := make([]byte, size)
			buf = buf[:runtime.Stack(buf, false)]
			log.Printf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
		}
	}()
	// ... リクエスト処理ロジック ...
}

64 << 10 はビットシフト演算子で、64 * 2^10、つまり 64 * 1024 を意味し、結果として65536バイト、すなわち64KBになります。この変更により、スタックトレースバッファのサイズが16倍に拡張されました。

コミットメッセージでは、「There's not a garbage generation issue, since this code should only be triggered when there's something seriously wrong with the program.」と述べられています。これは、このバッファの割り当てと使用は、プログラムが正常に動作している間は発生せず、深刻なエラー(パニック)が発生した場合にのみ行われるため、通常時のメモリ使用量やガベージコレクションのパフォーマンスには影響を与えないということを意味しています。つまり、この64KBのバッファは、デバッグ情報収集という特定の目的のために、必要な時にのみ一時的に確保されるものであり、通常のアプリケーション実行における「ガベージ生成」の問題とはならない、という設計判断が示されています。

この変更は、Goの net/http サーバーの堅牢性を高め、開発者がより効率的に問題を診断できるようにするための、実用的な改善と言えます。

コアとなるコードの変更箇所

--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -1083,7 +1083,7 @@ func validNPN(proto string) bool {
 func (c *conn) serve() {
 	defer func() {
 		if err := recover(); err != nil {
-			const size = 4096
+			const size = 64 << 10
 			buf := make([]byte, size)
 			buf = buf[:runtime.Stack(buf, false)]
 			log.Printf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)

コアとなるコードの解説

変更は src/pkg/net/http/server.go ファイルの (*conn).serve() メソッド内にあります。

このメソッドは、HTTP接続を処理するゴルーチン内で実行されます。その内部には defer 関数が定義されており、この defer 関数は recover() を呼び出すことで、serve() メソッドの実行中に発生したパニックを捕捉します。

捕捉されたパニックがある場合 (if err := recover(); err != nil)、以下の処理が行われます。

  1. const size = 64 << 10:

    • これがこのコミットの主要な変更点です。以前は 4096 (4KB) だった定数 size が、64 << 10 (64KB) に変更されました。この size は、スタックトレースを格納するためのバイトスライス buf の容量を決定します。
  2. buf := make([]byte, size):

    • 新しい size (64KB) の容量を持つバイトスライス buf が作成されます。このスライスが、runtime.Stack 関数によって書き込まれるスタックトレースのテキストを保持します。
  3. buf = buf[:runtime.Stack(buf, false)]:

    • runtime.Stack(buf, false) が呼び出され、現在のゴルーチンのスタックトレースが buf に書き込まれます。false は、現在のゴルーチンのみのトレースを取得することを意味します。
    • runtime.Stack は実際に書き込まれたバイト数を返すため、その戻り値を使って buf スライスの長さを調整しています。これにより、buf は実際にスタックトレースが占める正確なサイズにトリムされます。
  4. log.Printf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf):

    • 最後に、log.Printf を使用して、パニック情報がログに出力されます。出力される情報には、接続元のアドレス (c.remoteAddr)、パニックの値 (err)、そして取得されたスタックトレース (buf) が含まれます。

この変更により、パニック発生時に出力されるスタックトレースがより長くなり、デバッグに必要な情報が切り捨てられる可能性が大幅に減少しました。

関連リンク

参考にした情報源リンク