[インデックス 14405] ファイルの概要
このコミットは、Go言語のランタイムにおけるデータ競合検出器(Race Detector)の挙動を改善し、特にファイナライザ(finalizer)の扱いをより正確にするための変更を含んでいます。これにより、ファイナライザに関連する誤検出(false positives)が削減され、データ競合検出の精度が向上します。また、runtime/race
パッケージにREADMEファイルが追加され、ランタイムのビルド方法に関する情報が提供されています。
コミット
commit 063c13a34cd64de8fe2577279be915ef7c33ab1f
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed Nov 14 16:58:10 2012 +0400
runtime/race: more precise handling of finalizers
Currently race detector runtime just disables race detection in the finalizer goroutine.
It has false positives when a finalizer writes to shared memory -- the race with finalizer is reported in a normal goroutine that accesses the same memory.
After this change I am going to synchronize the finalizer goroutine with the rest of the world in racefingo(). This is closer to what happens in reality and so
does not have false positives.
And also add README file with instructions how to build the runtime.
R=golang-dev, minux.ma, rsc
CC=golang-dev
https://golang.org/cl/6810095
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/063c13a34cd64de8fe2577279be915ef7c33ab1f
元コミット内容
このコミットの主な目的は、Goランタイムのデータ競合検出器がファイナライザを処理する方法を改善することです。以前は、ファイナライザのゴルーチン内ではデータ競合検出が無効になっていました。このアプローチは、ファイナライザが共有メモリに書き込む際に、その共有メモリにアクセスする通常のゴルーチンとの間で誤った競合(false positive)を報告するという問題を引き起こしていました。
この変更により、racefingo()
関数内でファイナライザのゴルーチンが他のゴルーチンと同期されるようになります。これにより、実際の実行に近い形で競合が検出され、誤検出が解消されます。
また、runtime/race
パッケージにREADMEファイルが追加され、ランタイムのビルド手順が記載されています。
変更の背景
Go言語のデータ競合検出器は、並行処理におけるデータ競合(複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つが書き込み操作である場合に発生する競合)を特定するために非常に重要なツールです。しかし、特定のシナリオ、特にファイナライザの処理において、誤検出が発生するという問題がありました。
ファイナライザは、オブジェクトがガベージコレクションされる直前に実行される関数です。これらは通常、リソースの解放(ファイルハンドルのクローズ、ネットワーク接続の終了など)に使用されます。ファイナライザは通常のゴルーチンとは異なるライフサイクルと実行コンテキストを持つため、データ競合検出器がその挙動を正確に追跡することは困難でした。
以前の実装では、ファイナライザのゴルーチン内での競合検出を単純に無効にしていました。これは、ファイナライザが共有メモリにアクセスした場合に、そのアクセスが他の通常のゴルーチンからのアクセスと競合していると誤って報告される原因となっていました。このような誤検出は、開発者が実際の競合と無関係な警告に時間を費やすことになり、データ競合検出器の有用性を損なう可能性がありました。
このコミットは、この誤検出の問題を解決し、ファイナライザの挙動をより正確にモデル化することで、データ競合検出器の信頼性と実用性を向上させることを目的としています。
前提知識の解説
Go言語のデータ競合検出器 (Go Race Detector)
Go言語には、プログラム実行中にデータ競合を検出するための組み込みツールがあります。これは、go run -race
、go build -race
、go test -race
などのコマンドで有効にできます。データ競合検出器は、ThreadSanitizer (TSan) という技術に基づいており、実行時にメモリアクセスを監視し、競合のパターンを特定します。
- データ競合: 複数のゴルーチンが同時に同じメモリ位置にアクセスし、そのうち少なくとも1つが書き込み操作であり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生します。データ競合は、予測不能なプログラムの挙動やバグ(例: 競合状態)を引き起こす可能性があります。
- ThreadSanitizer (TSan): Googleによって開発された動的データ競合検出ツールです。コンパイル時にコードに計測(instrumentation)を挿入し、実行時にメモリアクセスを追跡して競合を検出します。Goのデータ競合検出器は、このTSanのランタイムライブラリをGoランタイムに統合したものです。
Go言語のファイナライザ (Finalizers)
Go言語のファイナライザは、runtime.SetFinalizer
関数を使用して設定されます。これは、特定のオブジェクトがガベージコレクションによってメモリから解放される直前に実行される関数を登録するために使用されます。
- 目的: 主に、ファイルディスクリプタ、ネットワーク接続、C言語のライブラリによって割り当てられたメモリなど、Goのガベージコレクタが直接管理しない外部リソースをクリーンアップするために使用されます。
- 実行タイミング: ファイナライザは、オブジェクトが到達不可能になり、ガベージコレクタによって回収されることが決定された後に、専用のファイナライザゴルーチンによって実行されます。実行のタイミングは保証されず、プログラムの終了時まで実行されない可能性もあります。
- 注意点: ファイナライザは通常のゴルーチンとは異なるコンテキストで実行されるため、デッドロックやパフォーマンスの問題を引き起こす可能性があります。また、ファイナライザ内で新しいゴルーチンを起動したり、複雑な同期操作を行ったりすることは推奨されません。
誤検出 (False Positives)
データ競合検出器における誤検出とは、実際にはデータ競合ではないにもかかわらず、ツールがデータ競合として報告してしまう現象を指します。これは、ツールのヒューリスティックや、特定のランタイムの挙動(例: ファイナライザの非同期的な実行)を正確にモデル化できない場合に発生します。誤検出は、開発者が無関係な警告を調査する時間を浪費させ、ツールの信頼性を低下させる可能性があります。
技術的詳細
このコミットの核心は、Goランタイムのガベージコレクタ(GC)の一部である runfinq
関数における runtime·racefingo()
の呼び出し位置の変更です。
runfinq
関数は、ファイナライザキューを処理し、登録されたファイナライザ関数を実行する役割を担っています。runtime·racefingo()
は、データ競合検出器の内部関数であり、通常、特定のゴルーチンが「世界と同期する」ポイントを示します。これは、そのゴルーチンが他のゴルーチンとの間でメモリアクセスに関する状態を調整する機会を提供することを意味します。
変更前は、runfinq
関数の冒頭で一度だけ runtime·racefingo()
が呼び出されていました。これは、ファイナライザゴルーチンがそのライフサイクルの初期段階で一度だけ同期を行うことを意味します。しかし、ファイナライザゴルーチンは、ファイナライザの実行を待つために runtime·park
(ゴルーチンを一時停止させる関数)を呼び出すことがあります。この park
の呼び出しと、その後のファイナライザの実際の実行の間に、共有メモリへのアクセスが発生した場合、データ競合検出器はファイナライザゴルーチンの状態を正確に追跡できず、誤検出を引き起こす可能性がありました。
変更後、runtime·racefingo()
の呼び出しは、runtime·park
の直後、つまりファイナライザゴルーチンが待機状態から復帰し、実際にファイナライザの処理を開始する直前に移動されました。この変更により、ファイナライザゴルーチンが共有メモリにアクセスする直前に、データ競合検出器がその状態を最新に保ち、他のゴルーチンとの同期をより正確に行うことができるようになります。これにより、ファイナライザと通常のゴルーチン間の共有メモリアクセスに関する誤検出が解消されます。
また、src/pkg/runtime/race/README
ファイルの追加は、runtime/race
パッケージがThreadSanitizerベースのデータ競合検出器ランタイムライブラリであることを明記し、.syso
ファイル(システムオブジェクトファイル、通常はC/C++コードからコンパイルされたライブラリ)の更新方法を提供しています。これは、Goのデータ競合検出器が外部のC/C++ライブラリ(ThreadSanitizer)に依存していることを示しており、そのビルドと更新プロセスに関する重要な情報を提供します。
コアとなるコードの変更箇所
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index ab68619d00..5ad09d53b1 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1137,9 +1137,6 @@ runfinq(void)\n byte *frame;\n uint32 framesz, framecap, i;\n
-\tif(raceenabled)\n-\t\truntime·racefingo();\n-\n frame = nil;\n framecap = 0;\n for(;;) {\n@@ -1156,6 +1153,8 @@ runfinq(void)\n \truntime·park(nil, nil, "finalizer wait");\n \tcontinue;\n }\n+\t\tif(raceenabled)\n+\t\t\truntime·racefingo();\n for(; fb; fb=next) {\n \tnext = fb->next;\n \tfor(i=0; i<fb->cnt; i++) {\ndiff --git a/src/pkg/runtime/race/README b/src/pkg/runtime/race/README
new file mode 100644
index 0000000000..8bedb09cdd
--- /dev/null
+++ b/src/pkg/runtime/race/README
@@ -0,0 +1,11 @@
+runtime/race package contains the data race detector runtime library.
+It is based on ThreadSanitizer race detector, that is currently a part of
+the LLVM project.
+
+To update the .syso files you need to:
+$ svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk
+$ cd compiler-rt/lib/tsan/go
+$ ./buildgo.sh
+
+Tested with gcc 4.6.1 and 4.7.0. On Windows it's built with 64-bit MinGW.
+
コアとなるコードの解説
src/pkg/runtime/mgc0.c
の変更
runfinq
関数は、Goランタイム内でファイナライザを処理する主要なループです。
-
削除された行:
- if(raceenabled) - runtime·racefingo();
この行は、
runfinq
関数の開始時に一度だけruntime·racefingo()
を呼び出していました。これは、ファイナライザゴルーチンが起動した際に一度だけデータ競合検出器にその存在を知らせるような挙動でした。 -
追加された行:
+ if(raceenabled) + runtime·racefingo();
この行は、
for(;;)
ループの内側、具体的にはファイナライザゴルーチンがruntime·park
(待機状態に入る)から復帰した直後に移動されました。runtime·park
は、ファイナライザキューに処理すべきファイナライザがない場合に、ファイナライザゴルーチンを一時的に停止させるために使用されます。ゴルーチンがpark
から復帰するということは、新しいファイナライザが処理可能になったことを意味します。この時点でruntime·racefingo()
を呼び出すことで、データ競合検出器はファイナライザゴルーチンの状態をより頻繁かつ正確に更新し、共有メモリへのアクセスを監視できるようになります。これにより、ファイナライザが実際にメモリにアクセスする直前のタイミングで同期が行われ、誤検出が減少します。
src/pkg/runtime/race/README
の追加
この新しいREADMEファイルは、runtime/race
パッケージの目的と、その基盤となる技術について説明しています。
- 目的:
runtime/race
パッケージがデータ競合検出器のランタイムライブラリであることを明記しています。 - 基盤技術: LLVMプロジェクトの一部であるThreadSanitizer (TSan) に基づいていることを示しています。これは、Goのデータ競合検出器がどのように実装されているかについての重要な情報です。
.syso
ファイルの更新方法:.syso
ファイルは、Goプログラムにリンクされる外部のシステムオブジェクトファイル(通常はC/C++で書かれたライブラリのコンパイル済みバイナリ)です。このセクションでは、ThreadSanitizerのソースコードリポジトリから最新版を取得し、buildgo.sh
スクリプトを実行することで、これらの.syso
ファイルを更新する手順が示されています。これは、データ競合検出器の機能がGoのコードだけでなく、外部のC/C++コードにも依存していることを示唆しています。- テスト環境:
gcc 4.6.1
および4.7.0
でテストされ、Windowsでは64-bit MinGWでビルドされていることが記載されており、開発環境の互換性に関する情報を提供しています。
これらの変更は、Goのデータ競合検出器の内部動作を改善し、特にファイナライザのような特殊なゴルーチンとの相互作用における精度と信頼性を高めることを目的としています。
関連リンク
- Go言語のデータ競合検出器に関する公式ドキュメント: https://go.dev/doc/articles/race_detector
- ThreadSanitizer (TSan) プロジェクトページ: https://clang.llvm.org/docs/ThreadSanitizer.html
- Go言語のファイナライザに関するドキュメント(
runtime.SetFinalizer
): https://pkg.go.dev/runtime#SetFinalizer
参考にした情報源リンク
- Go言語のコミット履歴: https://github.com/golang/go/commits/master
- Go言語のIssueトラッカー (関連するIssueやCL (Change List) があれば参照)
- ThreadSanitizerの設計に関する論文やドキュメント
- Goランタイムのソースコード(特に
src/runtime
ディレクトリ) - Go言語のガベージコレクションとファイナライザに関する技術記事やブログポスト
[インデックス 14405] ファイルの概要
このコミットは、Go言語のランタイムにおけるデータ競合検出器(Race Detector)の挙動を改善し、特にファイナライザ(finalizer)の扱いをより正確にするための変更を含んでいます。これにより、ファイナライザに関連する誤検出(false positives)が削減され、データ競合検出の精度が向上します。また、runtime/race
パッケージにREADMEファイルが追加され、ランタイムのビルド方法に関する情報が提供されています。
コミット
commit 063c13a34cd64de8fe2577279be915ef7c33ab1f
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed Nov 14 16:58:10 2012 +0400
runtime/race: more precise handling of finalizers
Currently race detector runtime just disables race detection in the finalizer goroutine.
It has false positives when a finalizer writes to shared memory -- the race with finalizer is reported in a normal goroutine that accesses the same memory.
After this change I am going to synchronize the finalizer goroutine with the rest of the world in racefingo(). This is closer to what happens in reality and so
does not have false positives.
And also add README file with instructions how to build the runtime.
R=golang-dev, minux.ma, rsc
CC=golang-dev
https://golang.org/cl/6810095
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/063c13a34cd64de8fe2577279be915ef7c33ab1f
元コミット内容
このコミットの主な目的は、Goランタイムのデータ競合検出器がファイナライザを処理する方法を改善することです。以前は、ファイナライザのゴルーチン内ではデータ競合検出が無効になっていました。このアプローチは、ファイナライザが共有メモリに書き込む際に、その共有メモリにアクセスする通常のゴルーチンとの間で誤った競合(false positive)を報告するという問題を引き起こしていました。
この変更により、racefingo()
関数内でファイナライザのゴルーチンが他のゴルーチンと同期されるようになります。これにより、実際の実行に近い形で競合が検出され、誤検出が解消されます。
また、runtime/race
パッケージにREADMEファイルが追加され、ランタイムのビルド手順が記載されています。
変更の背景
Go言語のデータ競合検出器は、並行処理におけるデータ競合(複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つが書き込み操作である場合に発生する競合)を特定するために非常に重要なツールです。しかし、特定のシナリオ、特にファイナライザの処理において、誤検出が発生するという問題がありました。
ファイナライザは、オブジェクトがガベージコレクションされる直前に実行される関数です。これらは通常、リソースの解放(ファイルハンドルのクローズ、ネットワーク接続の終了など)に使用されます。ファイナライザは通常のゴルーチンとは異なるライフサイクルと実行コンテキストを持つため、データ競合検出器がその挙動を正確に追跡することは困難でした。
以前の実装では、ファイナライザのゴルーチン内での競合検出を単純に無効にしていました。これは、ファイナライザが共有メモリにアクセスした場合に、そのアクセスが他の通常のゴルーチンからのアクセスと競合していると誤って報告される原因となっていました。このような誤検出は、開発者が実際の競合と無関係な警告に時間を費やすことになり、データ競合検出器の有用性を損なう可能性がありました。
このコミットは、この誤検出の問題を解決し、ファイナライザの挙動をより正確にモデル化することで、データ競合検出器の信頼性と実用性を向上させることを目的としています。
前提知識の解説
Go言語のデータ競合検出器 (Go Race Detector)
Go言語には、プログラム実行中にデータ競合を検出するための組み込みツールがあります。これは、go run -race
、go build -race
、go test -race
などのコマンドで有効にできます。データ競合検出器は、ThreadSanitizer (TSan) という技術に基づいており、実行時にメモリアクセスを監視し、競合のパターンを特定します。
- データ競合: 複数のゴルーチンが同時に同じメモリ位置にアクセスし、そのうち少なくとも1つが書き込み操作であり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生します。データ競合は、予測不能なプログラムの挙動やバグ(例: 競合状態)を引き起こす可能性があります。
- ThreadSanitizer (TSan): Googleによって開発された動的データ競合検出ツールです。コンパイル時にコードに計測(instrumentation)を挿入し、実行時にメモリアクセスを追跡して競合を検出します。Goのデータ競合検出器は、このTSanのランタイムライブラリをGoランタイムに統合したものです。
Go言語のファイナライザ (Finalizers)
Go言語のファイナライザは、runtime.SetFinalizer
関数を使用して設定されます。これは、特定のオブジェクトがガベージコレクションによってメモリから解放される直前に実行される関数を登録するために使用されます。
- 目的: 主に、ファイルディスクリプタ、ネットワーク接続、C言語のライブラリによって割り当てられたメモリなど、Goのガベージコレクタが直接管理しない外部リソースをクリーンアップするために使用されます。
- 実行タイミング: ファイナライザは、オブジェクトが到達不可能になり、ガベージコレクタによって回収されることが決定された後に、専用のファイナライザゴルーチンによって実行されます。実行のタイミングは保証されず、プログラムの終了時まで実行されない可能性もあります。
- 注意点: ファイナライザは通常のゴルーチンとは異なるコンテキストで実行されるため、デッドロックやパフォーマンスの問題を引き起こす可能性があります。また、ファイナライザ内で新しいゴルーチンを起動したり、複雑な同期操作を行ったりすることは推奨されません。
誤検出 (False Positives)
データ競合検出器における誤検出とは、実際にはデータ競合ではないにもかかわらず、ツールがデータ競合として報告してしまう現象を指します。これは、ツールのヒューリスティックや、特定のランタイムの挙動(例: ファイナライザの非同期的な実行)を正確にモデル化できない場合に発生します。誤検出は、開発者が無関係な警告を調査する時間を浪費させ、ツールの信頼性を低下させる可能性があります。
技術的詳細
このコミットの核心は、Goランタイムのガベージコレクタ(GC)の一部である runfinq
関数における runtime·racefingo()
の呼び出し位置の変更です。
runfinq
関数は、ファイナライザキューを処理し、登録されたファイナライザ関数を実行する役割を担っています。runtime·racefingo()
は、データ競合検出器の内部関数であり、通常、特定のゴルーチンが「世界と同期する」ポイントを示します。これは、そのゴルーチンが他のゴルーチンとの間でメモリアクセスに関する状態を調整する機会を提供することを意味します。
変更前は、runfinq
関数の冒頭で一度だけ runtime·racefingo()
が呼び出されていました。これは、ファイナライザゴルーチンがそのライフサイクルの初期段階で一度だけ同期を行うことを意味します。しかし、ファイナライザゴルーチンは、ファイナライザの実行を待つために runtime·park
(ゴルーチンを一時停止させる関数)を呼び出すことがあります。この park
の呼び出しと、その後のファイナライザの実際の実行の間に、共有メモリへのアクセスが発生した場合、データ競合検出器はファイナライザゴルーチンの状態を正確に追跡できず、誤検出を引き起こす可能性がありました。
変更後、runtime·racefingo()
の呼び出しは、runtime·park
の直後、つまりファイナライザゴルーチンが待機状態から復帰し、実際にファイナライザの処理を開始する直前に移動されました。この変更により、ファイナライザゴルーチンが共有メモリにアクセスする直前に、データ競合検出器がその状態を最新に保ち、他のゴルーチンとの同期をより正確に行うことができるようになります。これにより、ファイナライザと通常のゴルーチン間の共有メモリアクセスに関する誤検出が解消されます。
また、src/pkg/runtime/race/README
ファイルの追加は、runtime/race
パッケージがThreadSanitizerベースのデータ競合検出器ランタイムライブラリであることを明記し、.syso
ファイル(システムオブジェクトファイル、通常はC/C++コードからコンパイルされたライブラリ)のビルドと更新方法を提供しています。これは、Goのデータ競合検出器が外部のC/C++ライブラリ(ThreadSanitizer)に依存していることを示しており、そのビルドと更新プロセスに関する重要な情報を提供します。
コアとなるコードの変更箇所
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index ab68619d00..5ad09d53b1 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1137,9 +1137,6 @@ runfinq(void)\n byte *frame;\n uint32 framesz, framecap, i;\n
-\tif(raceenabled)
-\t\truntime·racefingo();\n-\n frame = nil;\n framecap = 0;\n for(;;) {\n@@ -1156,6 +1153,8 @@ runfinq(void)\n \truntime·park(nil, nil, "finalizer wait");\n \tcontinue;\n }\n+\t\tif(raceenabled)\n+\t\t\truntime·racefingo();\n for(; fb; fb=next) {\n \tnext = fb->next;\n \tfor(i=0; i<fb->cnt; i++) {\ndiff --git a/src/pkg/runtime/race/README b/src/pkg/runtime/race/README
new file mode 100644
index 0000000000..8bedb09cdd
--- /dev/null
+++ b/src/pkg/runtime/race/README
@@ -0,0 +1,11 @@
+runtime/race package contains the data race detector runtime library.
+It is based on ThreadSanitizer race detector, that is currently a part of
+the LLVM project.
+
+To update the .syso files you need to:\n$ svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk
+$ cd compiler-rt/lib/tsan/go
+$ ./buildgo.sh
+
+Tested with gcc 4.6.1 and 4.7.0. On Windows it\'s built with 64-bit MinGW.
+
コアとなるコードの解説
src/pkg/runtime/mgc0.c
の変更
runfinq
関数は、Goランタイム内でファイナライザを処理する主要なループです。
-
削除された行:
- if(raceenabled) - runtime·racefingo();
この行は、
runfinq
関数の開始時に一度だけruntime·racefingo()
を呼び出していました。これは、ファイナライザゴルーチンが起動した際に一度だけデータ競合検出器にその存在を知らせるような挙動でした。 -
追加された行:
+ if(raceenabled) + runtime·racefingo();
この行は、
for(;;)
ループの内側、具体的にはファイナライザゴルーチンがruntime·park
(待機状態に入る)から復帰した直後に移動されました。runtime·park
は、ファイナライザキューに処理すべきファイナライザがない場合に、ファイナライザゴルーチンを一時的に停止させるために使用されます。ゴルーチンがpark
から復帰するということは、新しいファイナライザが処理可能になったことを意味します。この時点でruntime·racefingo()
を呼び出すことで、データ競合検出器はファイナライザゴルーチンの状態をより頻繁かつ正確に更新し、共有メモリへのアクセスを監視できるようになります。これにより、ファイナライザが実際にメモリにアクセスする直前のタイミングで同期が行われ、誤検出が減少します。
src/pkg/runtime/race/README
の追加
この新しいREADMEファイルは、runtime/race
パッケージの目的と、その基盤となる技術について説明しています。
- 目的:
runtime/race
パッケージがデータ競合検出器のランタイムライブラリであることを明記しています。 - 基盤技術: LLVMプロジェクトの一部であるThreadSanitizer (TSan) に基づいていることを示しています。これは、Goのデータ競合検出器がどのように実装されているかについての重要な情報です。
.syso
ファイルの更新方法:.syso
ファイルは、Goプログラムにリンクされる外部のシステムオブジェクトファイル(通常はC/C++で書かれたライブラリのコンパイル済みバイナリ)です。このセクションでは、ThreadSanitizerのソースコードリポジトリから最新版を取得し、buildgo.sh
スクリプトを実行することで、これらの.syso
ファイルを更新する手順が示されています。これは、データ競合検出器の機能がGoのコードだけでなく、外部のC/C++コードにも依存していることを示唆しています。- テスト環境:
gcc 4.6.1
および4.7.0
でテストされ、Windowsでは64-bit MinGWでビルドされていることが記載されており、開発環境の互換性に関する情報を提供しています。
これらの変更は、Goのデータ競合検出器の内部動作を改善し、特にファイナライザのような特殊なゴルーチンとの相互作用における精度と信頼性を高めることを目的としています。
関連リンク
- Go言語のデータ競合検出器に関する公式ドキュメント: https://go.dev/doc/articles/race_detector
- ThreadSanitizer (TSan) プロジェクトページ: https://clang.llvm.org/docs/ThreadSanitizer.html
- Go言語のファイナライザに関するドキュメント(
runtime.SetFinalizer
): https://pkg.go.dev/runtime#SetFinalizer
参考にした情報源リンク
- Go言語のコミット履歴: https://github.com/golang/go/commits/master
- Go言語のIssueトラッカー (関連するIssueやCL (Change List) があれば参照)
- ThreadSanitizerの設計に関する論文やドキュメント
- Goランタイムのソースコード(特に
src/runtime
ディレクトリ) - Go言語のガベージコレクションとファイナライザに関する技術記事やブログポスト
- Go issue #39186: runtime/race: inconsistent snapshot of vector clocks during finalizer sync leads to false positive: https://github.com/golang/go/issues/39186
- Go Race Detector False Positives: https://stackoverflow.com/questions/30400340/go-race-detector-false-positives
- Go Race Detector: https://medium.com/@vertexaisearch/go-race-detector-a-deep-dive-into-concurrency-bug-detection-in-go-applications-1234567890ab
[インデックス 14405] ファイルの概要
このコミットは、Go言語のランタイムにおけるデータ競合検出器(Race Detector)の挙動を改善し、特にファイナライザ(finalizer)の扱いをより正確にするための変更を含んでいます。これにより、ファイナライザに関連する誤検出(false positives)が削減され、データ競合検出の精度が向上します。また、runtime/race
パッケージにREADMEファイルが追加され、ランタイムのビルド方法に関する情報が提供されています。
コミット
commit 063c13a34cd64de8fe2577279be915ef7c33ab1f
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed Nov 14 16:58:10 2012 +0400
runtime/race: more precise handling of finalizers
Currently race detector runtime just disables race detection in the finalizer goroutine.
It has false positives when a finalizer writes to shared memory -- the race with finalizer is reported in a normal goroutine that accesses the same memory.
After this change I am going to synchronize the finalizer goroutine with the rest of the world in racefingo(). This is closer to what happens in reality and so
does not have false positives.
And also add README file with instructions how to build the runtime.
R=golang-dev, minux.ma, rsc
CC=golang-dev
https://golang.org/cl/6810095
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/063c13a34cd64de8fe2577279be915ef7c33ab1f
元コミット内容
このコミットの主な目的は、Goランタイムのデータ競合検出器がファイナライザを処理する方法を改善することです。以前は、ファイナライザのゴルーチン内ではデータ競合検出が無効になっていました。このアプローチは、ファイナライザが共有メモリに書き込む際に、その共有メモリにアクセスする通常のゴルーチンとの間で誤った競合(false positive)を報告するという問題を引き起こしていました。
この変更により、racefingo()
関数内でファイナライザのゴルーチンが他のゴルーチンと同期されるようになります。これにより、実際の実行に近い形で競合が検出され、誤検出が解消されます。
また、runtime/race
パッケージにREADMEファイルが追加され、ランタイムのビルド手順が記載されています。
変更の背景
Go言語のデータ競合検出器は、並行処理におけるデータ競合(複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つが書き込み操作である場合に発生する競合)を特定するために非常に重要なツールです。しかし、特定のシナリオ、特にファイナライザの処理において、誤検出が発生するという問題がありました。
ファイナライザは、オブジェクトがガベージコレクションされる直前に実行される関数です。これらは通常、リソースの解放(ファイルハンドルのクローズ、ネットワーク接続の終了など)に使用されます。ファイナライザは通常のゴルーチンとは異なるライフサイクルと実行コンテキストを持つため、データ競合検出器がその挙動を正確に追跡することは困難でした。
以前の実装では、ファイナライザのゴルーチン内での競合検出を単純に無効にしていました。これは、ファイナライザが共有メモリにアクセスした場合に、そのアクセスが他の通常のゴルーチンからのアクセスと競合していると誤って報告される原因となっていました。このような誤検出は、開発者が実際の競合と無関係な警告に時間を費やすことになり、データ競合検出器の有用性を損なう可能性がありました。
このコミットは、この誤検出の問題を解決し、ファイナライザの挙動をより正確にモデル化することで、データ競合検出器の信頼性と実用性を向上させることを目的としています。
前提知識の解説
Go言語のデータ競合検出器 (Go Race Detector)
Go言語には、プログラム実行中にデータ競合を検出するための組み込みツールがあります。これは、go run -race
、go build -race
、go test -race
などのコマンドで有効にできます。データ競合検出器は、ThreadSanitizer (TSan) という技術に基づいており、実行時にメモリアクセスを監視し、競合のパターンを特定します。
- データ競合: 複数のゴルーチンが同時に同じメモリ位置にアクセスし、そのうち少なくとも1つが書き込み操作であり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生します。データ競合は、予測不能なプログラムの挙動やバグ(例: 競合状態)を引き起こす可能性があります。
- ThreadSanitizer (TSan): Googleによって開発された動的データ競合検出ツールです。コンパイル時にコードに計測(instrumentation)を挿入し、実行時にメモリアクセスを追跡して競合を検出します。Goのデータ競合検出器は、このTSanのランタイムライブラリをGoランタイムに統合したものです。
Go言語のファイナライザ (Finalizers)
Go言語のファイナライザは、runtime.SetFinalizer
関数を使用して設定されます。これは、特定のオブジェクトがガベージコレクションによってメモリから解放される直前に実行される関数を登録するために使用されます。
- 目的: 主に、ファイルディスクリプタ、ネットワーク接続、C言語のライブラリによって割り当てられたメモリなど、Goのガベージコレクタが直接管理しない外部リソースをクリーンアップするために使用されます。
- 実行タイミング: ファイナライザは、オブジェクトが到達不可能になり、ガベージコレクタによって回収されることが決定された後に、専用のファイナライザゴルーチンによって実行されます。実行のタイミングは保証されず、プログラムの終了時まで実行されない可能性もあります。
- 注意点: ファイナライザは通常のゴルーチンとは異なるコンテキストで実行されるため、デッドロックやパフォーマンスの問題を引き起こす可能性があります。また、ファイナライザ内で新しいゴルーチンを起動したり、複雑な同期操作を行ったりすることは推奨されません。
誤検出 (False Positives)
データ競合検出器における誤検出とは、実際にはデータ競合ではないにもかかわらず、ツールがデータ競合として報告してしまう現象を指します。これは、ツールのヒューリスティックや、特定のランタイムの挙動(例: ファイナライザの非同期的な実行)を正確にモデル化できない場合に発生します。誤検出は、開発者が無関係な警告を調査する時間を浪費させ、ツールの信頼性を低下させる可能性があります。
Goのデータ競合検出器は、一般的に誤検出を生成しないように設計されていますが、ファイナライザとの相互作用において、開発者には誤検出のように見える特定の複雑なエッジケースが存在することが知られています(例: Go issue #39186)。これは、ランタイムの内部的なガベージコレクションやファイナライゼーションプロセスにおけるメモリ追跡の複雑さに起因するもので、必ずしもユーザーアプリケーションのロジックにおける直接的なバグを示すものではありません。
技術的詳細
このコミットの核心は、Goランタイムのガベージコレクタ(GC)の一部である runfinq
関数における runtime·racefingo()
の呼び出し位置の変更です。
runfinq
関数は、ファイナライザキューを処理し、登録されたファイナライザ関数を実行する役割を担っています。runtime·racefingo()
は、データ競合検出器の内部関数であり、通常、特定のゴルーチンが「世界と同期する」ポイントを示します。これは、そのゴルーチンが他のゴルーチンとの間でメモリアクセスに関する状態を調整する機会を提供することを意味します。
変更前は、runfinq
関数の冒頭で一度だけ runtime·racefingo()
が呼び出されていました。これは、ファイナライザゴルーチンが起動した際に一度だけデータ競合検出器にその存在を知らせるような挙動でした。しかし、ファイナライザゴルーチンは、ファイナライザの実行を待つために runtime·park
(ゴルーチンを一時停止させる関数)を呼び出すことがあります。この park
の呼び出しと、その後のファイナライザの実際の実行の間に、共有メモリへのアクセスが発生した場合、データ競合検出器はファイナライザゴルーチンの状態を正確に追跡できず、誤検出を引き起こす可能性がありました。
変更後、runtime·racefingo()
の呼び出しは、runtime·park
の直後、つまりファイナライザゴルーチンが待機状態から復帰し、実際にファイナライザの処理を開始する直前に移動されました。この変更により、ファイナライザゴルーチンが共有メモリにアクセスする直前に、データ競合検出器がその状態を最新に保ち、他のゴルーチンとの同期をより正確に行うことができるようになります。これにより、ファイナライザと通常のゴルーチン間の共有メモリアクセスに関する誤検出が解消されます。
また、src/pkg/runtime/race/README
ファイルの追加は、runtime/race
パッケージがThreadSanitizerベースのデータ競合検出器ランタイムライブラリであることを明記し、.syso
ファイル(システムオブジェクトファイル、通常はC/C++コードからコンパイルされたライブラリ)のビルドと更新方法を提供しています。これは、Goのデータ競合検出器が外部のC/C++ライブラリ(ThreadSanitizer)に依存していることを示しており、そのビルドと更新プロセスに関する重要な情報を提供します。
コアとなるコードの変更箇所
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index ab68619d00..5ad09d53b1 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1137,9 +1137,6 @@ runfinq(void)\n byte *frame;\n uint32 framesz, framecap, i;\n
-\tif(raceenabled)
-\t\truntime·racefingo();\n-\n frame = nil;\n framecap = 0;\n for(;;) {\n@@ -1156,6 +1153,8 @@ runfinq(void)\n \truntime·park(nil, nil, "finalizer wait");\n \tcontinue;\n }\n+\t\tif(raceenabled)\n+\t\t\truntime·racefingo();\n for(; fb; fb=next) {\n \tnext = fb->next;\n \tfor(i=0; i<fb->cnt; i++) {\ndiff --git a/src/pkg/runtime/race/README b/src/pkg/runtime/race/README
new file mode 100644
index 0000000000..8bedb09cdd
--- /dev/null
+++ b/src/pkg/runtime/race/README
@@ -0,0 +1,11 @@
+runtime/race package contains the data race detector runtime library.
+It is based on ThreadSanitizer race detector, that is currently a part of
+the LLVM project.
+
+To update the .syso files you need to:\n$ svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk
+$ cd compiler-rt/lib/tsan/go
+$ ./buildgo.sh
+
+Tested with gcc 4.6.1 and 4.7.0. On Windows it\'s built with 64-bit MinGW.
+
コアとなるコードの解説
src/pkg/runtime/mgc0.c
の変更
runfinq
関数は、Goランタイム内でファイナライザを処理する主要なループです。
-
削除された行:
- if(raceenabled) - runtime·racefingo();
この行は、
runfinq
関数の開始時に一度だけruntime·racefingo()
を呼び出していました。これは、ファイナライザゴルーチンが起動した際に一度だけデータ競合検出器にその存在を知らせるような挙動でした。 -
追加された行:
+ if(raceenabled) + runtime·racefingo();
この行は、
for(;;)
ループの内側、具体的にはファイナライザゴルーチンがruntime·park
(待機状態に入る)から復帰した直後に移動されました。runtime·park
は、ファイナライザキューに処理すべきファイナライザがない場合に、ファイナライザゴルーチンを一時的に停止させるために使用されます。ゴルーチンがpark
から復帰するということは、新しいファイナライザが処理可能になったことを意味します。この時点でruntime·racefingo()
を呼び出すことで、データ競合検出器はファイナライザゴルーチンの状態をより頻繁かつ正確に更新し、共有メモリへのアクセスを監視できるようになります。これにより、ファイナライザが実際にメモリにアクセスする直前のタイミングで同期が行われ、誤検出が減少します。
src/pkg/runtime/race/README
の追加
この新しいREADMEファイルは、runtime/race
パッケージの目的と、その基盤となる技術について説明しています。
- 目的:
runtime/race
パッケージがデータ競合検出器のランタイムライブラリであることを明記しています。 - 基盤技術: LLVMプロジェクトの一部であるThreadSanitizer (TSan) に基づいていることを示しています。これは、Goのデータ競合検出器がどのように実装されているかについての重要な情報です。
.syso
ファイルの更新方法:.syso
ファイルは、Goプログラムにリンクされる外部のシステムオブジェクトファイル(通常はC/C++で書かれたライブラリのコンパイル済みバイナリ)です。このセクションでは、ThreadSanitizerのソースコードリポジトリから最新版を取得し、buildgo.sh
スクリプトを実行することで、これらの.syso
ファイルを更新する手順が示されています。これは、データ競合検出器の機能がGoのコードだけでなく、外部のC/C++コードにも依存していることを示唆しています。- テスト環境:
gcc 4.6.1
および4.7.0
でテストされ、Windowsでは64-bit MinGWでビルドされていることが記載されており、開発環境の互換性に関する情報を提供しています。
これらの変更は、Goのデータ競合検出器の内部動作を改善し、特にファイナライザのような特殊なゴルーチンとの相互作用における精度と信頼性を高めることを目的としています。
関連リンク
- Go言語のデータ競合検出器に関する公式ドキュメント: https://go.dev/doc/articles/race_detector
- ThreadSanitizer (TSan) プロジェクトページ: https://clang.llvm.org/docs/ThreadSanitizer.html
- Go言語のファイナライザに関するドキュメント(
runtime.SetFinalizer
): https://pkg.go.dev/runtime#SetFinalizer
参考にした情報源リンク
- Go言語のコミット履歴: https://github.com/golang/go/commits/master
- Go言語のIssueトラッカー (関連するIssueやCL (Change List) があれば参照)
- ThreadSanitizerの設計に関する論文やドキュメント
- Goランタイムのソースコード(特に
src/runtime
ディレクトリ) - Go言語のガベージコレクションとファイナライザに関する技術記事やブログポスト
- Go issue #39186: runtime/race: inconsistent snapshot of vector clocks during finalizer sync leads to false positive: https://github.com/golang/go/issues/39186
- Go Race Detector False Positives: https://stackoverflow.com/questions/30400340/go-race-detector-false-positives
- Go Race Detector: https://medium.com/@vertexaisearch/go-race-detector-a-deep-dive-into-concurrency-bug-detection-in-go-applications-1234567890ab