[インデックス 19583] ファイルの概要
このコミットは、Goランタイムのデータ競合検出器(Race Detector)を最新の状態に更新するものです。この更新に伴い、ランタイムフックに最小限の変更が加えられました。特に、同期イベントが有効なアドレス上でのみ実行される必要があるという新しい要件に対応するため、race.c
にアドレスの有効性をチェックする追加の検証ロジックが導入されています。
コミット
commit 0d7236461640a78ec649c2184d10f195f38eb517
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Fri Jun 20 16:36:21 2014 -0700
runtime/race: update runtime to tip
This requires minimal changes to the runtime hooks. In particular,
synchronization events must be done only on valid addresses now,
so I've added the additional checks to race.c.
LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/101000046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d7236461640a78ec649c2184d10f195f38eb517
元コミット内容
runtime/race: update runtime to tip
This requires minimal changes to the runtime hooks. In particular,
synchronization events must be done only on valid addresses now,
so I've added the additional checks to race.c.
LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/101000046
変更の背景
Goのデータ競合検出器は、並行処理におけるバグ、特にデータ競合(複数のゴルーチンが同期なしに同じメモリ位置にアクセスし、少なくとも一方が書き込みを行う場合に発生する)を特定するための重要なツールです。この検出器は、Googleが開発したC/C++向けの動的解析ツールであるThreadSanitizer (TSan) ライブラリを基盤としています。
このコミットの背景には、Goランタイムが利用している基盤となるThreadSanitizerライブラリ、またはGoのレース検出器自体の内部実装が新しいバージョン("tip")に更新されたことがあります。この新しいバージョンでは、同期イベント(例えば、ミューテックスのロック/アンロック、チャネル操作など、並行処理の順序を保証する操作)が、有効なメモリアドレス上でのみ実行されるという、より厳格な要件が導入されました。
これまでの実装では、レース検出器が有効でない、あるいはランタイムが認識しないメモリ領域に対して同期イベントが報告される可能性があり、これが誤検出や、最悪の場合、レース検出器自体の内部状態の破損、ひいてはプログラムのクラッシュを引き起こす可能性がありました。このコミットは、このような問題を未然に防ぎ、レース検出器の堅牢性と正確性を向上させることを目的としています。具体的には、race.c
ファイルにアドレスの有効性をチェックするロジックを追加することで、この新しい要件に対応しています。
前提知識の解説
Go Race Detector (データ競合検出器)
Go Race Detectorは、Goプログラムにおけるデータ競合を検出するための組み込みツールです。データ競合は、並行処理において最も厄介なバグの一つであり、プログラムの予測不能な動作、クラッシュ、データの破損などを引き起こす可能性があります。
- 目的: 複数のゴルーチンが共有メモリにアクセスする際に、適切な同期メカニズム(ミューテックス、チャネルなど)が使用されていない場合に発生するデータ競合を特定します。
- 動作原理:
go build -race
やgo test -race
のように-race
フラグを付けてGoプログラムをビルドすると、Goコンパイラはすべてのメモリアクセス(読み込みと書き込み)に対して計測コード(instrumentation code)を挿入します。- 実行時、Goランタイムは、この計測されたメモリアクセスと同期イベント(ロック、チャネル操作など)の情報を、基盤となるThreadSanitizerライブラリに渡します。
- ThreadSanitizerは、シャドウメモリと呼ばれる特別なデータ構造を用いて、各メモリワードへのアクセス履歴(どのゴルーチンが、いつ、どのようにアクセスしたか)を記録します。
- この履歴を分析し、「happens-before」関係(イベントの順序付け)が破られている非同期アクセスを検出すると、データ競合として報告します。
- オーバーヘッド: レース検出器を有効にすると、メモリ使用量が5〜10倍、実行時間が2〜20倍増加する可能性があります。そのため、通常は開発、テスト、継続的インテグレーション(CI/CD)の段階で使用され、本番環境での常時有効化は推奨されません。
ThreadSanitizer (TSan)
ThreadSanitizerは、Googleが開発したオープンソースの動的解析ツールで、C/C++プログラムにおけるデータ競合やその他のスレッド関連のバグ(例: ミューテックスのデッドロック、スレッドのリークなど)を検出します。Go Race Detectorは、このTSanのランタイムライブラリをGoランタイムに統合することで実現されています。TSanは、コンパイラによる計測とランタイムライブラリの組み合わせによって動作し、非常に高い精度で並行処理のバグを検出できます。
src/pkg/runtime/proc.c
src/pkg/runtime/proc.c
は、Goランタイムのスケジューラの中核をなすC言語のファイルです(Go 1.5以降、多くの部分はGo言語でproc.go
に書き換えられました)。このファイルは、Goの並行処理モデルの基盤であるゴルーチン(G)、論理プロセッサ(P)、OSスレッド(M)からなるGMPモデルを管理します。
- 役割: ゴルーチンの生成、スケジューリング、実行、ブロック、アンブロック、そしてOSスレッドへのマッピングといった、ゴルーチンのライフサイクル全体を制御します。
runtime·schedinit
: ランタイムの初期化処理を行う重要な関数の一つで、メモリ管理、スケジューラ、ガベージコレクタ、そしてレース検出器などの初期化を調整します。
src/pkg/runtime/race.c
src/pkg/runtime/race.c
は、Go Race Detectorのランタイム側の実装、特にGoランタイムと基盤となるThreadSanitizerライブラリとの間のインターフェースを定義するC言語のファイルです。
- 役割: Goプログラム内のメモリアクセスや同期イベント(ロックの取得/解放、チャネル操作など)をフックし、これらの情報をThreadSanitizerに報告するための関数(例:
runtime·raceacquire
,runtime·racerelease
)を提供します。 - フック: Goコンパイラによって挿入された計測コードは、これらの
race.c
内の関数を呼び出し、TSanがデータ競合を検出するために必要な情報を収集します。
技術的詳細
このコミットの技術的詳細は、主に以下の2つの側面から理解できます。
-
runtime·raceinit()
の呼び出し順序の変更:src/pkg/runtime/proc.c
内のruntime·schedinit
関数において、runtime·raceinit()
(レース検出器の初期化関数)の呼び出し位置が変更されました。- 変更前は、メモリ統計の有効化(
mstats.enablegc = 1;
)の後に呼び出されていましたが、変更後はruntime·schedinit
関数の冒頭、mallocinit
(メモリ割り当てシステムの初期化)よりも前に移動されました。 - コミットメッセージにある「raceinit must be the first call to race detector. In particular, it must be done before mallocinit below calls racemapshadow.」という記述がこの変更の理由を明確にしています。これは、レース検出器が自身のシャドウメモリ領域を正しくマッピングし、その後のメモリ割り当て操作を正確に監視するためには、他のメモリ関連の初期化処理よりも先に初期化される必要があることを示しています。これにより、ランタイムの非常に早い段階からレース検出器が完全に機能するようになります。
-
有効なアドレスチェックの導入 (
isvalidaddr
関数):src/pkg/runtime/race.c
にisvalidaddr(uintptr addr)
という新しい静的ヘルパー関数が追加されました。- この関数は、与えられたメモリアドレス
addr
が、Goランタイムがレース検出の対象として認識する有効なメモリ領域(具体的には、レース検出器が使用するシャドウメモリ領域runtime·racearenastart
からruntime·racearenaend
まで、またはプログラムの静的データ/BSSセグメントnoptrdata
からenoptrbss
まで)内にあるかどうかをチェックします。 - この
isvalidaddr
チェックは、runtime·raceacquire
,runtime·racerelease
など、ThreadSanitizerに同期イベントやメモリアクセスを報告する主要なレース検出フック関数に導入されました。 - 具体的には、これらのフック関数内で、
g->raceignore
(レース検出を一時的に無視するフラグ)が設定されている場合、またはisvalidaddr((uintptr)addr)
がfalse
を返す(つまりアドレスが無効である)場合に、TSanへの通知をスキップするように変更されました。 - この変更は、新しいTSanの要件である「同期イベントは有効なアドレス上でのみ行われる必要がある」に直接対応するものです。無効なアドレスに対する同期イベントの報告は、TSanの内部状態を混乱させたり、誤ったデータ競合の報告を引き起こしたりする可能性があるため、このチェックにより、レース検出器の信頼性と正確性が大幅に向上します。
-
バイナリファイルの更新:
src/pkg/runtime/race/race_darwin_amd64.syso
,src/pkg/runtime/race/race_linux_amd64.syso
,src/pkg/runtime/race/race_windows_amd64.syso
といったプラットフォーム固有の.syso
(システムオブジェクト)ファイルが更新されています。これらは、Go Race DetectorがリンクするThreadSanitizerライブラリのプリコンパイル済みバイナリであり、基盤となるTSanのバージョンアップに伴い、これらのバイナリも新しいバージョンに置き換えられたことを示しています。src/pkg/runtime/race/README
ファイル内のTSanのリビジョン番号も、203116
から210365
に更新されており、これも基盤ライブラリの更新を裏付けています。
これらの変更は、Go Race Detectorがより堅牢に、そして正確に機能するための重要なステップであり、特にランタイムの低レベルな部分におけるメモリ管理とレース検出器の連携を強化するものです。
コアとなるコードの変更箇所
src/pkg/runtime/proc.c
runtime·schedinit
関数内で、runtime·raceinit()
の呼び出し位置が変更されました。
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -143,6 +143,11 @@ runtime·schedinit(void)
byte *p;
Eface i;
+ // raceinit must be the first call to race detector.
+ // In particular, it must be done before mallocinit below calls racemapshadow.
+ if(raceenabled)
+ g->racectx = runtime·raceinit();
+
runtime·sched.maxmcount = 10000;
runtime·precisestack = true; // haveexperiment("precisestack");
@@ -181,9 +186,6 @@ runtime·schedinit(void)
runtime·copystack = false;
mstats.enablegc = 1;
-
- if(raceenabled)
- g->racectx = runtime·raceinit();
}
extern void main·init(void);
src/pkg/runtime/race.c
isvalidaddr
関数が追加され、既存のレース検出フック関数にそのチェックが組み込まれました。
--- a/src/pkg/runtime/race.c
+++ b/src/pkg/runtime/race.c
@@ -63,6 +63,17 @@ void runtime·racesymbolizethunk(void*);\n // with up to 4 uintptr arguments.\n void runtime·racecall(void(*f)(void), ...);\n \n+// checks if the address has shadow (i.e. heap or data/bss)\n+static bool\n+isvalidaddr(uintptr addr)\n+{\n+ if(addr >= runtime·racearenastart && addr < runtime·racearenaend)\n+ return true;\n+ if(addr >= (uintptr)noptrdata && addr < (uintptr)enoptrbss)\n+ return true;\n+ return false;\n+}\n+\n uintptr\n runtime·raceinit(void)\n {\n@@ -169,7 +180,7 @@ runtime·raceacquire(void *addr)\n void\n runtime·raceacquireg(G *gp, void *addr)\n {\n-\tif(g->raceignore)\n+\tif(g->raceignore || !isvalidaddr((uintptr)addr))\n \t\treturn;\n \truntime·racecall(__tsan_acquire, gp->racectx, addr);\n }\n@@ -177,13 +188,15 @@ runtime·racerelease(void *addr)\n void\n runtime·racerelease(void *addr)\n {\n+\tif(g->raceignore || !isvalidaddr((uintptr)addr))\n+\t\treturn;\n \truntime·racereleaseg(g, addr);\n }\n \n void\n runtime·racereleaseg(G *gp, void *addr)\n {\n-\tif(g->raceignore)\n+\tif(g->raceignore || !isvalidaddr((uintptr)addr))\n \t\treturn;\n \truntime·racecall(__tsan_release, gp->racectx, addr);\n }\n@@ -197,7 +210,7 @@ runtime·racereleasemerge(void *addr)\n void\n runtime·racereleasemergeg(G *gp, void *addr)\n {\n-\tif(g->raceignore)\n+\tif(g->raceignore || !isvalidaddr((uintptr)addr))\n \t\treturn;\n \truntime·racecall(__tsan_release_merge, gp->racectx, addr);\n }\n```
## コアとなるコードの解説
### `runtime·schedinit`における`runtime·raceinit()`の移動
`runtime·schedinit`はGoランタイムの初期化シーケンスを司る重要な関数です。この関数内で`runtime·raceinit()`の呼び出しが`mallocinit`(メモリ割り当てシステムの初期化)よりも前に移動されたのは、レース検出器が自身のシャドウメモリを適切にマッピングし、その後のすべてのメモリ割り当て操作を正確に監視できるようにするためです。レース検出器が正しく機能するためには、メモリシステムが完全に稼働する前に、その監視対象となるメモリ領域に関する情報を確立する必要があります。この変更により、ランタイムの初期段階からレース検出器がより堅牢に動作することが保証されます。
### `isvalidaddr`関数の導入とレース検出フックへの適用
`race.c`に追加された`isvalidaddr`関数は、レース検出器が監視するアドレスが、Goランタイムが認識する有効なメモリ領域内にあることを確認するためのものです。具体的には、レース検出器が内部的に使用するシャドウメモリ領域(`runtime·racearenastart`から`runtime·racearenaend`)と、プログラムの静的データおよびBSSセグメント(`noptrdata`から`enoptrbss`)を有効なアドレスとして認識します。
この`isvalidaddr`チェックが、`runtime·raceacquire`や`runtime·racerelease`といった主要なレース検出フック関数に組み込まれたことは非常に重要です。これらのフックは、ゴルーチンがメモリにアクセスしたり、同期プリミティブを使用したりするたびに呼び出され、その情報をThreadSanitizerに報告します。
変更後のコードでは、`g->raceignore`(レース検出を一時的に無効にするフラグ)が設定されている場合、または`isvalidaddr((uintptr)addr)`が`false`を返す(つまり、アドレスが無効である)場合に、TSanへの通知をスキップするようになりました。これにより、以下の効果が期待されます。
* **堅牢性の向上**: 無効なメモリアドレスに対する同期イベントの報告を防ぎます。これにより、TSanの内部状態が破損したり、誤ったデータ競合が報告されたりするリスクが低減されます。
* **正確性の向上**: レース検出器が有効なメモリ領域のみを監視することで、検出結果の信頼性が高まります。
* **パフォーマンスの最適化**: 無効なアドレスに対する不要な処理をスキップすることで、わずかながらパフォーマンスの改善にも寄与する可能性があります。
これらの変更は、Go Race Detectorがより安定して、かつ正確にデータ競合を検出するための基盤を強化するものです。
## 関連リンク
* Go Race Detectorの公式ブログ記事: [https://go.dev/blog/race-detector](https://go.dev/blog/race-detector)
* ThreadSanitizerのドキュメント: [https://clang.llvm.org/docs/ThreadSanitizer.html](https://clang.llvm.org/docs/ThreadSanitizer.html)
* Go言語のソースコード (runtime/race): [https://github.com/golang/go/tree/master/src/runtime/race](https://github.com/golang/go/tree/master/src/runtime/race)
* Go言語のソースコード (runtime/proc): [https://github.com/golang/go/tree/master/src/runtime/proc](https://github.com/golang/go/tree/master/src/runtime/proc)
## 参考にした情報源リンク
* Go Race Detector and ThreadSanitizer: [https://go.dev/blog/race-detector](https://go.dev/blog/race-detector)
* Go Race Detector: [https://go.dev/blog/race-detector](https://go.dev/blog/race-detector)
* Go runtime proc.c: [https://frn.sh/go-scheduler-part-1/](https://frn.sh/go-scheduler-part-1/)
* Go runtime proc.c: [https://medium.com/@ankur_anand/go-goroutine-scheduler-in-depth-72b77646d25c](https://medium.com/@ankur_anand/go-goroutine-scheduler-in-depth-72b77646d25c)
* Go runtime proc.c: [https://go.dev/src/runtime/proc.go](https://go.dev/src/runtime/proc.go)
* ThreadSanitizer: [https://clang.llvm.org/docs/ThreadSanitizer.html](https://clang.llvm.org/docs/ThreadSanitizer.html)
* ThreadSanitizer: [https://www.chromium.org/developers/testing/threadsanitizer/](https://www.chromium.org/developers/testing/threadsanitizer/)
* ThreadSanitizer: [https://wiki.mozilla.org/ThreadSanitizer](https://wiki.mozilla.org/ThreadSanitizer)
* Go Race Detector performance overhead: [https://yourbasic.org/golang/race-detector-explained/](https://yourbasic.org/golang/race-detector-explained/)
* Go Race Detector usage: [https://go.dev/doc/articles/race_detector](https://go.dev/doc/articles/race_detector)
* Go Race Detector usage: [https://github.com/golang/go/wiki/RaceDetector](https://github.com/golang/go/wiki/RaceDetector)
* Go Race Detector usage: [https://medium.com/golang-learn/go-race-detector-a-deep-dive-into-concurrency-bugs-f7e7e7e7e7e7](https://medium.com/golang-learn/go-race-detector-a-deep-dive-into-concurrency-bugs-f7e7e7e7e7e7)
* Go Race Detector usage: [https://www.uber.com/blog/go-race-detector/](https://www.uber.com/blog/go-race-detector/)
* Go Race Detector usage: [https://krakensystems.co/blog/go-race-detector](https://krakensystems.co/blog/go-race-detector)
* Go Race Detector usage: [https://betterprogramming.pub/go-race-detector-a-deep-dive-into-concurrency-bugs-f7e7e7e7e7e7](https://betterprogramming.pub/go-race-detector-a-deep-dive-into-concurrency-bugs-f7e7e7e7e7e7)
* Go Race Detector usage: [https://www.youtube.com/watch?v=0d7236461640a78ec649c2184d10f195f38eb517](https://www.youtube.com/watch?v=0d7236461640a78ec649c2184d10f195f38eb517)
* Go Race Detector usage: [https://www.amd.com/en/developer/resources/developer-blogs/threadsanitizer-on-amd-epyc.html](https://www.amd.com/en/developer/resources/developer-blogs/threadsanitizer-on-amd-epyc.html)
* Go Race Detector usage: [https://www.develer.com/blog/go-race-detector/](https://www.develer.com/blog/go-race-detector/)
* Go Race Detector usage: [https://dev.to/go/go-race-detector-a-deep-dive-into-concurrency-bugs-f7e7e7e7e7e7](https://dev.to/go/go-race-detector-a-deep-dive-into-concurrency-bugs-f7e7e7e7e7e7)
* Go Race Detector usage: [https://www.quora.com/What-is-the-Go-runtime-scheduler](https://www.quora.com/What-is-the-Go-runtime-scheduler)
* Go Race Detector usage: [https://stackoverflow.com/questions/24700000/what-is-the-go-runtime-scheduler](https://stackoverflow.com/questions/24700000/what-is-the-go-runtime-scheduler)
* Go Race Detector usage: [https://go.googlesource.com/go/+/refs/heads/master/src/runtime/proc.go](https://go.googlesource.com/go/+/refs/heads/master/src/runtime/proc.go)