[インデックス 19354] ファイルの概要
このコミットは、Go言語のテストスイートにおけるtest/fixedbugs/issue4388.go
という特定のテストケースが、Plan 9環境で不安定(flakey)になる問題を修正するものです。具体的には、runtime.Caller
関数が返すスタックフレーム情報、特に<autogenerated>:1
という特殊なファイル名と行番号の組み合わせに関する挙動が、Plan 9上で期待通りにならない場合があるため、テストのロジックをより堅牢にする変更が加えられています。
コミット
- コミットハッシュ:
147a21456e2b2255d1c7e487cb9f53386791c357
- Author: Mikio Hara mikioh.mikioh@gmail.com
- Date: Thu May 15 06:39:15 2014 +0900
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/147a21456e2b2255d1c7e487cb9f53386791c357
元コミット内容
test: fix flakey test case for issue 4388
Seems like we need to drag the stack for <autogenerated>:1 on Plan 9.
See http://build.golang.org/log/283b996102b833dd81c58301d78aceaa4fe9838b.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/95390043
変更の背景
この変更の背景には、Go言語のテスト環境における特定の不安定性があります。コミットメッセージにある「flakey test case for issue 4388」は、テストが時々失敗し、時々成功するという非決定的な挙動を示していたことを意味します。これは、テストが特定の環境やタイミングに依存して結果が変わる場合に発生します。
具体的には、runtime.Caller
関数がスタックフレーム情報を取得する際に、<autogenerated>:1
という特殊なソース位置を期待するテストが、Plan 9という特定のオペレーティングシステム環境で不安定になっていました。コミットメッセージの「Seems like we need to drag the stack for runtime.Caller
が期待するスタックフレームをすぐに見つけられない場合があることを示唆しています。
「issue 4388」の具体的な内容は公開されているGoのIssueトラッカーからは直接確認できませんでしたが、これは内部的なバグトラッキングシステムでの識別子であるか、あるいは非常に古い、現在はアーカイブされている問題である可能性があります。しかし、コミットメッセージとコードの変更内容から、runtime.Caller
の挙動とスタックトレースの解析に関する問題であったことは明らかです。
前提知識の解説
1. runtime.Caller
関数
runtime.Caller(skip int)
は、Go言語のruntime
パッケージが提供する関数で、呼び出し元の関数のファイル名、行番号、および関数名を取得するために使用されます。skip
引数は、スタックフレームをどれだけスキップするかを指定します。skip=0
はCaller
関数自身の呼び出し元、skip=1
はCaller
を呼び出した関数の呼び出し元、といった具合です。
例えば、以下のようなコードがあったとします。
package main
import (
"fmt"
"runtime"
)
func foo() {
bar()
}
func bar() {
_, file, line, _ := runtime.Caller(1) // bar()の呼び出し元 (foo) の情報を取得
fmt.Printf("Called from: %s:%d\n", file, line)
}
func main() {
foo()
}
この場合、bar()
内でruntime.Caller(1)
を呼び出すと、foo()
の呼び出し元の情報(main.go
のfoo()
が呼び出された行)が取得されます。
2. スタックトレースとスタックフレーム
プログラムが実行される際、関数呼び出しの履歴は「コールスタック(Call Stack)」に積まれていきます。各関数呼び出しは「スタックフレーム(Stack Frame)」としてスタックに記録され、そのフレームには関数の引数、ローカル変数、戻りアドレスなどの情報が含まれます。runtime.Caller
は、このコールスタックを遡って特定のスタックフレームの情報を取得するメカニズムを提供します。
3. <autogenerated>:1
Go言語のコンパイラやランタイムは、デバッグ情報やプロファイリングのために、ソースコードには直接存在しない「自動生成された」コードのスタックフレームを生成することがあります。これらのフレームは、実際のファイルパスを持たないため、<autogenerated>
という特別なファイル名と、通常は1
という行番号で識別されます。これは、Goの内部的な処理や、特定の最適化、あるいはテストハーネスのようなメタプログラミング的な状況で現れることがあります。
4. Plan 9
Plan 9 from Bell Labsは、ベル研究所で開発された分散オペレーティングシステムです。Go言語の設計者の一部(Ken ThompsonやRob Pikeなど)はPlan 9の開発にも深く関わっており、Go言語のツールチェイン(アセンブラ、コンパイラ、リンカ)やアセンブラの構文は、Plan 9の対応するものから直接派生しています。
Goのランタイム、特にスタック管理にはPlan 9の影響が色濃く残っています。Goのゴルーチンは動的にリサイズ可能なスタックを使用しますが、Plan 9のような特定のOSでは、OS固有の関数(シグナルハンドリングなど)のためにゴルーチンのスタック上に固定サイズの領域を予約するといった、OS固有のスタック割り当ての考慮事項があります。このため、runtime.Caller
のようなスタックを操作する関数が、Plan 9環境で他のOSとは異なる挙動を示す可能性がありました。
技術的詳細
このコミットが修正しようとしている問題は、runtime.Caller
が<autogenerated>:1
という特定のスタックフレームを期待する際に、Plan 9環境ではそのフレームが期待されるskip
レベル(n
)に存在しない場合があるというものです。
元のコードでは、runtime.Caller(n)
を一度だけ呼び出し、その結果が<autogenerated>:1
であるかをチェックしていました。しかし、Plan 9のスタックの特性(「drag the stack」という表現が示唆するように、スタックフレームの配置や深さが他のOSと異なる、あるいは動的に変化する可能性がある)により、期待する<autogenerated>:1
のフレームが、n
で指定された正確な位置に常に存在するとは限りませんでした。場合によっては、より深いスタックレベルに隠れている可能性があったのです。
この問題を解決するため、変更後のコードではruntime.Caller
をループ内で複数回呼び出すように修正されています。具体的には、i
を1
からn
までインクリメントしながらruntime.Caller(i)
を呼び出し、それぞれの呼び出しで返されるファイル名と行番号が<autogenerated>:1
と一致するかをチェックします。これにより、期待する<autogenerated>:1
のフレームがスタックのどこか(1
からn
の範囲内)に存在すれば、テストは成功するようになります。
これは、特定のスタックフレームが常に固定のskip
レベルにあるとは限らないという、OSやコンパイラの挙動に起因する非決定性を吸収するための堅牢なアプローチです。特に、テストコードではこのような非決定的な挙動を排除し、安定した結果を得ることが非常に重要です。
コアとなるコードの変更箇所
変更はtest/fixedbugs/issue4388.go
ファイル内のcheckLine
関数に集中しています。
--- a/test/fixedbugs/issue4388.go
+++ b/test/fixedbugs/issue4388.go
@@ -43,8 +43,14 @@ func checkLine(n int) {
if err := recover(); err == nil {
panic("did not panic")
}
- _, file, line, _ := runtime.Caller(n)
- if file != "<autogenerated>" || line != 1 {
- panic(fmt.Sprintf("expected <autogenerated>:1 have %s:%d", file, line))
+ var file string
+ var line int
+ for i := 1; i <= n; i++ {
+ _, file, line, _ = runtime.Caller(i)
+ if file != "<autogenerated>" || line != 1 {
+ continue
+ }
+ return
}
+ panic(fmt.Sprintf("expected <autogenerated>:1 have %s:%d", file, line))
}
コアとなるコードの解説
変更されたcheckLine
関数は、以下のロジックで動作します。
- パニックの確認: まず、
recover()
を使ってパニックが発生したことを確認します。パニックが発生していない場合はテスト失敗とします。 - スタックフレームの探索:
file
とline
変数を宣言します。for i := 1; i <= n; i++
というループを開始します。このループは、runtime.Caller
のskip
引数を1
からn
まで順に試行します。- ループ内で
_, file, line, _ = runtime.Caller(i)
を呼び出し、現在のi
レベルでのスタックフレーム情報を取得します。 - 取得した
file
が"<autogenerated>"
であり、かつline
が1
であるかをチェックします。 - もし条件が一致すれば、期待するスタックフレームが見つかったことになるため、関数を
return
して正常終了します。 - 条件が一致しない場合は
continue
し、次のi
の値でスタックをさらに深く探索します。
- エラーハンドリング: ループが
n
まで到達しても<autogenerated>:1
のスタックフレームが見つからなかった場合、panic
を発生させ、期待するフレームが見つからなかったことを報告します。これにより、テストは失敗します。
この変更により、runtime.Caller
が返すスタックフレームの正確なskip
レベルがPlan 9環境で変動しても、テストが堅牢に動作するようになりました。テストは、1
からn
までの任意のskip
レベルで<autogenerated>:1
が見つかれば成功と判断するため、より柔軟に対応できます。
関連リンク
- Go Change-ID:
https://golang.org/cl/95390043
- ビルドログ:
http://build.golang.org/log/283b996102b833dd81c58301d78aceaa4fe9838b
参考にした情報源リンク
- Go言語の
runtime.Caller
に関するドキュメント(一般的な情報) - Go言語のスタック管理とPlan 9の関連性に関する情報(Web検索結果)
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGdjXcC81eqY59TIvVS7to7O8kJsIx9ViKsoC1bJfHMXnJCWzBST2lmfxc3CUJq3rnMbm1QPDaVPorHIxnf9D-Ur04lSwe-6HaMwZFSo3qJ1FMlp-x4CF9FYHtmwVwpvZaa9AVkqgGPCgw_4UGSUDDFT3nyu3dVE6GrjHcEjz4t4vxyiozWX9SmniaqW71Q
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFqF18XCyLzaji-qNaxMHDO-Wl4CfkmGG167dr0xhWXUVmcalVTy-XDUmScwv24EhjPy3-WuwBMX8d4pkOB__lCFZUi2s_oeP2dKPXQAkoSWj5_c2w=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHqM9MSKCweUFg42G4BECG8SIkJCBEpw2pBSTxoadanmwOY4apK8eAsEkc0rVN373nNKN8Fmg7YrFAiS1TgXsKoNV6RhTkkIgcLBbgQqRbL2VNMHTzjRqb6n7GGyc7adJ8qKFDwAw4eLyVWJg_5CzxqJHaPmWpOzV8=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHLTYgMg1sBW__QMcIlO8RcfMhcWJEAVMP4F_9tnhi9RTjWl33czrb57cynXSajrX0djeG8OUXbpoyGHtgGV5WKXpsxOjt6MYqVlfQtJ3UfJnoouxB3s1lUXc-j4S2
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQESHe-AVu56ANKFDg-T2bD51YZg8aIiIX5eqhRSqtmilNOd2jpyYYenGbiBUKD4PTRnoEW1fWO2962Jqu-jMni4jfh4M1fqJhvMX-mwaMt1sqGXwwP1HR4fDDYH6tuE3u6eG5p9uoeqww==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG8yoXpx5u6MokVflAH-rKqMrsqCgj5ddvxwmK-yb_rn-OM8KKaMgSHDGGU0VV9SWhVM1BfECsLGRr8qu3jc4KmNIFj0DjVZvvjjDyHj4ISJVXR_9GcvGeV1TS10tlOQDYIO48=