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

[インデックス 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.blockspecialaddrootが常にベースアドレスで動作することが期待されるわけではない場合、この変更が予期せぬ動作を引き起こす可能性もあります。

このコミットでは、この問題を解決するために、新しいローカル変数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が使用されます。

この変更により、以下の利点が得られます。

  1. スタックトレースの正確性: vが変更されないため、スタックトレースがaddfinrootsに渡された元のポインタの値を正確に表示できるようになります。これはデバッグにおいて非常に重要です。
  2. ファイナライザ処理の堅牢性: 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は、ファイナライザが設定されたオブジェクトへのポインタです。

  1. void *base;の追加: addfinroots関数の冒頭に、新しいローカル変数void *base;が追加されました。この変数は、runtime.mlookupが返すメモリブロックのベースアドレスを格納するために使用されます。

  2. 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の値は変更されずに保持されます。

  3. runtime.blockspecialの引数変更: 変更前: !runtime·blockspecial(v) 変更後: !runtime·blockspecial(base)

    runtime.mlookupによって取得された正しいベースアドレスbaseruntime.blockspecialに渡されるようになりました。これにより、メモリブロックの特別な性質のチェックが、常に正しいベースアドレスに対して行われることが保証されます。

  4. addrootの引数変更: 変更前: addroot((Obj){v, size, 0}); 変更後: addroot((Obj){base, size, 0});

    addroot関数は、ガベージコレクタがマークすべきオブジェクトのルートを追加するために使用されます。ここでも、runtime.mlookupによって取得された正しいベースアドレスbaseが使用されるようになりました。これにより、ファイナライザが設定されたオブジェクトのメモリ領域が、GCによって正しくマークされることが保証されます。

これらの変更により、addfinroots関数は、元のポインタの値を保持しつつ、ファイナライザのルートを正確に処理できるようになり、スタックトレースの正確性とGCの堅牢性が向上しました。

関連リンク

参考にした情報源リンク

  • コミットメッセージおよび差分情報
  • Go言語のランタイム、ガベージコレクション、ファイナライザに関する一般的な知識