[インデックス 15233] ファイルの概要
このコミットは、Go言語のランタイムにおけるガベージコレクション(GC)の一部であるファイナライザの処理に関する修正です。具体的には、runtime/mgc0.c
ファイル内のaddfinroots
関数が変更され、ファイナライザのルートを追加する際に元のポインタの値を保持するように調整されています。これにより、スタックトレースがより正確な情報を示すようになり、ファイナライザの整合性の問題が修正されます。
コミット
commit e5dae3baaa34c8cd596eb34d6619272921122f4d
Author: Russ Cox <rsc@golang.org>
Date: Wed Feb 13 22:31:45 2013 -0500
runtime: tweak addfinroots to preserve original pointer
Use local variable so that stack trace will show value of v.
Fixes #4790.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/7300106
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e5dae3baaa34c8cd596eb34d6619272921122f4d
元コミット内容
runtime: tweak addfinroots to preserve original pointer
Use local variable so that stack trace will show value of v.
Fixes #4790.
変更の背景
このコミットは、Goランタイムのガベージコレクション(GC)におけるファイナライザの処理に関する問題を解決するために行われました。コミットメッセージには「Fixes #4790
」と記載されており、これはGoのIssueトラッカーにおける特定のバグ報告に対応する修正であることを示しています。
具体的な問題は、addfinroots
関数内でポインタv
が変更されることで、スタックトレースが元のポインタの正しい値を示さなくなる可能性があったことです。ガベージコレクタがオブジェクトをマークする際に、ファイナライザが設定されたオブジェクトのルートを正しく追跡し、そのオブジェクトがまだ参照されているかどうかを判断する必要があります。もしv
が関数内で変更されてしまうと、runtime.mlookup
のような関数が期待する元のポインタではなく、変更されたポインタでメモリを検索しようとし、結果として「mark - finalizer inconsistency
」というエラーを引き起こす可能性がありました。
この修正の目的は、addfinroots
関数がファイナライザのルートを処理する際に、元のポインタの値を保持し、スタックトレースの正確性を確保し、ファイナライザの整合性に関する潜在的な問題を解消することにありました。
前提知識の解説
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション(GC)、スケジューリング、メモリ管理、システムコールインターフェースなどが含まれます。Goプログラムは、コンパイル時にGoランタイムとリンクされ、実行時にランタイムの機能を利用します。
ガベージコレクション (Garbage Collection, GC)
Goは自動メモリ管理を採用しており、ガベージコレクタが不要になったメモリを自動的に解放します。GoのGCは並行マーク&スイープ方式を採用しており、プログラムの実行と並行して動作し、アプリケーションの一時停止(STW: Stop The World)時間を最小限に抑えるように設計されています。GCの主なフェーズには、マークフェーズ(到達可能なオブジェクトを識別する)とスイープフェーズ(到達不能なオブジェクトが占めるメモリを解放する)があります。
ファイナライザ (Finalizers)
Goのファイナライザは、オブジェクトがガベージコレクタによってメモリから解放される直前に実行される関数です。runtime.SetFinalizer
関数を使ってオブジェクトにファイナライザを設定できます。ファイナライザは、ファイルハンドルやネットワーク接続などの外部リソースをクリーンアップするために使用されることがあります。ファイナライザが設定されたオブジェクトは、GCがそのオブジェクトが到達不能であると判断した後、ファイナライザが実行されるまでメモリに保持されます。
runtime.mlookup
runtime.mlookup
は、Goランタイム内部で使用される関数で、指定されたアドレスがどのメモリブロックに属しているかを検索します。この関数は、ガベージコレクタがポインタをたどってオブジェクトをマークする際に、ポインタが有効なメモリ領域を指しているか、そのメモリ領域のサイズはどのくらいかなどを判断するために利用されます。
runtime.blockspecial
runtime.blockspecial
もGoランタイム内部の関数で、特定のメモリブロックが特別な意味を持つかどうかをチェックします。例えば、ファイナライザが設定されたオブジェクトのメモリブロックは、GCにとって特別な扱いが必要となる場合があります。
技術的詳細
このコミットの技術的な核心は、addfinroots
関数におけるポインタv
の扱いを変更した点にあります。
元のコードでは、addfinroots
関数に渡されたvoid *v
というポインタが、runtime.mlookup
の第二引数として渡されていました。runtime.mlookup
は、第二引数に渡されたポインタの値を、見つかったメモリブロックのベースアドレスで上書きする可能性があります。
// 変更前
if(!runtime·mlookup(v, &v, &size, nil) || !runtime·blockspecial(v))
この行では、v
のアドレスがruntime.mlookup
に渡され、runtime.mlookup
が成功すると、v
自体がメモリブロックのベースアドレスに更新されます。その結果、次の行のruntime.blockspecial(v)
やaddroot((Obj){v, size, 0});
が、元のv
の値ではなく、runtime.mlookup
によって変更されたv
の値(つまりベースアドレス)を使用することになります。
問題は、スタックトレースが元のv
の値(addfinroots
に渡された時点のポインタ)を期待している場合、v
が関数内で変更されてしまうと、スタックトレースが不正確になる可能性があることです。また、runtime.blockspecial
やaddroot
が常にベースアドレスで動作することが期待されるわけではない場合、この変更が予期せぬ動作を引き起こす可能性もあります。
このコミットでは、この問題を解決するために、新しいローカル変数void *base;
を導入しました。
// 変更後
void *base; // 新しいローカル変数
// ...
if(!runtime·mlookup(v, &base, &size, nil) || !runtime·blockspecial(base))
// ...
addroot((Obj){base, size, 0});
変更後では、runtime.mlookup
の第二引数に&base
を渡すことで、runtime.mlookup
が見つけたベースアドレスをbase
変数に格納するようにしました。これにより、元のv
の値はaddfinroots
関数内で変更されずに保持されます。そして、runtime.blockspecial(base)
とaddroot((Obj){base, size, 0});
では、runtime.mlookup
によって取得された正しいベースアドレスbase
が使用されます。
この変更により、以下の利点が得られます。
- スタックトレースの正確性:
v
が変更されないため、スタックトレースがaddfinroots
に渡された元のポインタの値を正確に表示できるようになります。これはデバッグにおいて非常に重要です。 - ファイナライザ処理の堅牢性:
runtime.mlookup
が返すベースアドレスを明示的にbase
変数に格納し、それ以降の処理でbase
を使用することで、ファイナライザのルート追加処理がより堅牢になります。これにより、「mark - finalizer inconsistency
」のようなエラーの発生を防ぐことができます。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -1255,13 +1255,14 @@ static void
addfinroots(void *v)
{
uintptr size;
+ void *base;
size = 0;
- if(!runtime·mlookup(v, &v, &size, nil) || !runtime·blockspecial(v))
+ if(!runtime·mlookup(v, &base, &size, nil) || !runtime·blockspecial(base))
runtime·throw("mark - finalizer inconsistency");
// do not mark the finalizer block itself. just mark the things it points at.
- addroot((Obj){v, size, 0});
+ addroot((Obj){base, size, 0});
}
static void
コアとなるコードの解説
変更されたのはsrc/pkg/runtime/mgc0.c
ファイル内のaddfinroots
関数です。
addfinroots
関数は、ファイナライザが設定されたオブジェクトのルートをガベージコレクタに通知するために呼び出されます。引数void *v
は、ファイナライザが設定されたオブジェクトへのポインタです。
-
void *base;
の追加:addfinroots
関数の冒頭に、新しいローカル変数void *base;
が追加されました。この変数は、runtime.mlookup
が返すメモリブロックのベースアドレスを格納するために使用されます。 -
runtime.mlookup
の引数変更: 変更前:if(!runtime·mlookup(v, &v, &size, nil) || !runtime·blockspecial(v))
変更後:if(!runtime·mlookup(v, &base, &size, nil) || !runtime·blockspecial(base))
以前は
runtime.mlookup
の第二引数に&v
が渡され、v
自体が上書きされていました。この変更により、&base
が渡されることで、runtime.mlookup
が取得したベースアドレスはbase
変数に格納され、元のv
の値は変更されずに保持されます。 -
runtime.blockspecial
の引数変更: 変更前:!runtime·blockspecial(v)
変更後:!runtime·blockspecial(base)
runtime.mlookup
によって取得された正しいベースアドレスbase
がruntime.blockspecial
に渡されるようになりました。これにより、メモリブロックの特別な性質のチェックが、常に正しいベースアドレスに対して行われることが保証されます。 -
addroot
の引数変更: 変更前:addroot((Obj){v, size, 0});
変更後:addroot((Obj){base, size, 0});
addroot
関数は、ガベージコレクタがマークすべきオブジェクトのルートを追加するために使用されます。ここでも、runtime.mlookup
によって取得された正しいベースアドレスbase
が使用されるようになりました。これにより、ファイナライザが設定されたオブジェクトのメモリ領域が、GCによって正しくマークされることが保証されます。
これらの変更により、addfinroots
関数は、元のポインタの値を保持しつつ、ファイナライザのルートを正確に処理できるようになり、スタックトレースの正確性とGCの堅牢性が向上しました。
関連リンク
- Go CL (Code Review) 7300106: https://golang.org/cl/7300106
参考にした情報源リンク
- コミットメッセージおよび差分情報
- Go言語のランタイム、ガベージコレクション、ファイナライザに関する一般的な知識