[インデックス 18796] ファイルの概要
このコミットは、Goランタイムのガベージコレクション(GC)に関連するmgc0.c
ファイル内のrunfinq
関数におけるメモリリークを修正するものです。特に、sync.Pool
のファイナライザテストが失敗する原因となっていた、C言語で書かれた関数内の未初期化データが古いメモリ領域を指し続ける問題に対処しています。
コミット
commit b08156cd874d9534776cd9ece8f6f4ab092a68a5
Author: Russ Cox <rsc@golang.org>
Date: Fri Mar 7 11:27:01 2014 -0500
runtime: fix memory leak in runfinq
One reason the sync.Pool finalizer test can fail is that
this function's ef1 contains uninitialized data that just
happens to point at some of the old pool. I've seen this cause
retention of a single pool cache line (32 elements) on arm.
Really we need liveness information for C functions, but
for now we can be more careful about data in long-lived
C functions that block.
LGTM=bradfitz, dvyukov
R=golang-codereviews, bradfitz, dvyukov
CC=golang-codereviews, iant, khr
https://golang.org/cl/72490043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b08156cd874d9534776cd9ece8f6f4ab092a68a5
元コミット内容
Goランタイムのrunfinq
関数におけるメモリリークを修正します。sync.Pool
のファイナライザテストが失敗する一因として、この関数のef1
変数が未初期化データを含み、それがたまたま古いプールの一部を指してしまうことが挙げられます。特にARMアーキテクチャでは、これにより単一のプールキャッシュライン(32要素)が保持され続けてしまう現象が確認されていました。
本来であればC関数に対するライブネス情報(変数が今後使用されるかどうかを示す情報)が必要ですが、当面の間は、長時間ブロックするC関数内のデータに対してより注意を払うことで対応します。
変更の背景
Goのガベージコレクタは、到達不能になったオブジェクトを自動的に解放しますが、ファイナライザが設定されたオブジェクトは、GCによって到達不能と判断された後、ファイナライザが実行されるまでメモリが解放されません。sync.Pool
はオブジェクトの再利用を目的としたもので、GCの負荷を軽減するために使用されます。しかし、sync.Pool
にオブジェクトを戻すためにファイナライザを使用するパターンは、GCがオブジェクトを解放しようとするたびにファイナライザがオブジェクトをプールに戻し、結果的にオブジェクトが再び到達可能になるというアンチパターンを引き起こす可能性があります。これにより、GCがオブジェクトを解放できず、メモリリークが発生する可能性があります。
このコミットの背景には、sync.Pool
のファイナライザテストが失敗するという具体的な問題がありました。調査の結果、GoランタイムのC言語で実装されたrunfinq
関数内で使用されるローカル変数、特にEface
型のef1
が、適切に初期化またはゼロクリアされていない場合に、古いsync.Pool
のデータへの参照を保持し続けることが判明しました。C言語の関数はGoのランタイムが持つような詳細なライブネス情報を持たないため、コンパイラが変数を最適化する際に、その変数が古いポインタを保持し続ける可能性がありました。特にARMのような特定のアーキテクチャでこの問題が顕在化したのは、メモリの配置やコンパイラの最適化の挙動が影響していると考えられます。
この問題は、本来解放されるべきメモリが解放されずに残り続けるため、メモリ使用量が増加し、最終的にはシステムのパフォーマンス低下やメモリ枯渇につながる可能性がありました。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルのシステム。ガベージコレクション、スケジューリング、メモリ管理など、Go言語の多くの重要な機能を提供します。Goランタイムの一部はC言語やアセンブリ言語で書かれています。
- ガベージコレクション (Garbage Collection, GC): プログラムが動的に割り当てたメモリのうち、もはや使用されない(到達不能な)領域を自動的に解放するプロセス。GoのGCは並行・並行(concurrent and parallel)なマーク&スイープ方式を採用しています。
- ファイナライザ (Finalizer):
runtime.SetFinalizer
関数を使ってオブジェクトに設定できる特殊な関数。オブジェクトがガベージコレクタによって到達不能と判断され、メモリが解放される直前に実行されます。主にファイルディスクリプタやネットワーク接続など、Goのメモリ管理外のリソースを解放するために使用されます。ただし、ファイナライザの実行タイミングは保証されず、メモリリークの原因となるアンチパターンも存在します。 sync.Pool
: 一時的なオブジェクトの再利用を目的としたGoの標準ライブラリの機能。オブジェクトの生成と破棄のコストを削減し、GCの負荷を軽減します。使用済みのオブジェクトをプールに戻し、必要に応じてプールから取得することで、オブジェクトの再利用を促進します。Eface
(Empty Interface): Goの空インターフェース(interface{}
)の内部表現。Goのインターフェースは、内部的には型情報と値(または値へのポインタ)の2つのワードで構成されます。Eface
は、任意の型の値を保持できるため、Goの型システムにおける柔軟性の基盤となります。- ライブネス情報 (Liveness Information): プログラム解析の概念で、ある時点である変数が将来のどこかで参照される可能性があるかどうかを示す情報。ガベージコレクタは、この情報に基づいてオブジェクトが到達可能かどうかを判断します。C言語のコンパイラはGoランタイムのような詳細なライブネス情報を常に生成するわけではないため、GoのGCがC関数内のポインタを正確に追跡するのが難しい場合があります。
USED
マクロ: GoランタイムのC言語部分で使われるマクロ。これは、コンパイラが最適化によって「未使用」と判断し、レジスタから削除してしまう可能性のある変数を、意図的にメモリにフラッシュさせる(メモリ上に存在させる)ために使用されます。これにより、GCがその変数を正しくスキャンできるようになります。
技術的詳細
このコミットは、Goランタイムのsrc/pkg/runtime/mgc0.c
ファイル内のrunfinq
関数に焦点を当てています。mgc0.c
は、Goの初期のガベージコレクタの実装の一部であり、runfinq
関数はファイナライザキューを処理し、登録されたファイナライザを実行する役割を担っていました。
問題の核心は、C言語で書かれたrunfinq
関数が、GoのGCが持つような詳細なライブネス情報を持たない点にありました。これにより、関数内のローカル変数(特にEface
型のef1
)が、以前のファイナライザ実行で処理されたオブジェクトへの古いポインタを保持し続ける可能性がありました。これらのポインタがゼロクリアされないままだと、GCはそれらのポインタが指すメモリ領域がまだ「到達可能」であると誤って判断し、結果としてメモリが解放されずにリークが発生していました。
コミットメッセージで言及されている「sync.Pool
finalizer test can fail」という点は重要です。sync.Pool
はオブジェクトの再利用を促進しますが、もしファイナライザがオブジェクトをsync.Pool
に戻すようなロジックを含んでいた場合、GCがオブジェクトを解放しようとするたびにファイナライザが実行され、オブジェクトがプールに戻されることで、GCがそのオブジェクトを解放できなくなるという循環参照のような状態が発生し、メモリリークを引き起こす可能性があります。このコミットは、そのようなアンチパターンによって引き起こされるメモリリークを、runfinq
関数自体の内部的な問題として修正しています。
修正は、runfinq
関数内の主要なローカル変数を明示的にnil
(またはゼロ値)に初期化し、ループの各イテレーションの終わりに再度ゼロクリアすることで行われます。これにより、これらの変数が古いポインタを保持し続けることを防ぎ、GCが正しくメモリを解放できるようになります。
また、USED(&f);
のようなUSED
マクロの使用は、Cコンパイラがこれらの変数を最適化によって削除しないようにするためのものです。これにより、変数がメモリ上に確実に存在し、GCがそれらをスキャンして到達可能性を判断できるようになります。
コアとなるコードの変更箇所
src/pkg/runtime/mgc0.c
ファイルのrunfinq
関数に以下の変更が加えられました。
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -2525,8 +2525,29 @@ runfinq(void)
uint32 framesz, framecap, i;
Eface *ef, ef1;
+ // This function blocks for long periods of time, and because it is written in C
+ // we have no liveness information. Zero everything so that uninitialized pointers
+ // do not cause memory leaks.
+ f = nil;
+ fb = nil;
+ next = nil;
frame = nil;
framecap = 0;
+ framesz = 0;
+ i = 0;
+ ef = nil;
+ ef1.type = nil;
+ ef1.data = nil;
+
+ // force flush to memory
+ USED(&f);
+ USED(&fb);
+ USED(&next);
+ USED(&framesz);
+ USED(&i);
+ USED(&ef);
+ USED(&ef1);
+
for(;;) {
runtime·lock(&gclock);
fb = finq;
@@ -2581,6 +2602,16 @@ runfinq(void)
finc = fb;
runtime·unlock(&gclock);
}
+
+ // Zero everything that's dead, to avoid memory leaks.
+ // See comment at top of function.
+ f = nil;
+ fb = nil;
+ next = nil;
+ i = 0;
+ ef = nil;
+ ef1.type = nil;
+ ef1.data = nil;
runtime·gc(1); // trigger another gc to clean up the finalized objects, if possible
}
}
コアとなるコードの解説
変更は主にrunfinq
関数の冒頭と、無限ループの各イテレーションの終わりに集中しています。
-
関数の冒頭での初期化とゼロクリア:
f = nil; fb = nil; next = nil;
frame = nil; framecap = 0; framesz = 0; i = 0;
ef = nil; ef1.type = nil; ef1.data = nil;
これらの行は、runfinq
関数が開始される際に、関数内で使用されるポインタ型変数(f
,fb
,next
,frame
,ef
)およびEface
型のef1
の各フィールド(type
,data
)を明示的にnil
またはゼロ値に初期化しています。これにより、これらの変数が以前の実行や未初期化状態から古いメモリ参照を保持することを防ぎます。
-
USED
マクロの適用:USED(&f); USED(&fb); USED(&next); USED(&framesz); USED(&i); USED(&ef); USED(&ef1);
これらのUSED
マクロは、Cコンパイラに対して、これらの変数が「使用されている」ことを示し、最適化によってレジスタから削除されたり、メモリからフラッシュされたりしないように指示します。これにより、GoのGCがこれらの変数をスキャンする際に、それらが指すメモリ領域の到達可能性を正しく判断できるようになります。これは、C関数がGoのGCのような詳細なライブネス情報を持たないことへの対処策です。
-
ループ内でのゼロクリア:
f = nil; fb = nil; next = nil; i = 0; ef = nil; ef1.type = nil; ef1.data = nil;
for(;;)
ループの各イテレーションの終わりに、これらの変数が再度nil
またはゼロ値に設定されます。これは、ファイナライザの処理が完了し、次のGCがトリガーされる前に、これらの変数が古い参照を保持しないようにするためです。これにより、長期にわたるrunfinq
の実行中にメモリリークが発生するリスクをさらに低減します。
これらの変更は、C言語で書かれたGoランタイムの低レベル部分におけるメモリ管理の堅牢性を向上させ、特にsync.Pool
のようなGoの高度な機能と連携する際の潜在的なメモリリークを防ぐことを目的としています。
関連リンク
- Go言語の
sync.Pool
に関する公式ドキュメント: https://pkg.go.dev/sync#Pool - Go言語のファイナライザに関する公式ドキュメント (
runtime.SetFinalizer
): https://pkg.go.dev/runtime#SetFinalizer - Goのガベージコレクションに関する一般的な情報: https://go.dev/doc/gc-guide
参考にした情報源リンク
- Goの
sync.Pool
とファイナライザのアンチパターンに関する議論:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG626aMgyympE43JfNYXvuVfRbIWni-etaPje2CRnuOM8tWRvUerNY6Ou8N5BHmy-uJt96d82lQpbByUMrZfYc9hdMKBaF8naF0qZIaHsHN1sDhUtmYMxqe44XlyogwiIDEltkfikyJiyBKGoTQcEc=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHTyz4ux3BCqiZTSmUAIGhtydXYVbtRii76ZyLrlmG5uWdujxG5v4CvtkC3JB1r5WxFx5wlF-9IQW3QNMxrWA0xL-z7dqSILtriginvIEEYYEvX9DHQcK8sy0SUTYBnPSgtZ0DU1FvQ6ZUc_dQVbcXCBdAm1vappfNJCB_QSgpgOFbVgBln_MZBaKieDmQ4BANUQv-Yr8lP
- GoランタイムのC関数におけるライブネス情報とファイナライザの課題:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGj9hFEWuRflpC431J02RQ0A5qvOu8geDF34Dswo4HESYMoWRakwC0ec_MkdtZrTtyUTRAgyyUdD5agKs7PS9o-WJyyMX4ogSX-1xTyy42Up1LxMA8VfOR6eZQSyuq4OhSrnHLue3KrwqUKt-PqJfOZ0pfukbgGdrbgbxjNgbNWqjpzJVE=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFB0VVrteKMysPg4rVxEFlo-BLbmhhdPhvHQk-oKQdzbhYQlpkblRX-NpZxfBU5PrG68PB0GPrcQ-y6Wu_8p_DzQ9q44gTZASHCq1XpJwSdBqfaPCDdT91WowSNGvE=
mgc0.c
,runfinq
,Eface
に関するGoランタイムの内部構造:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEO4r73Dt22FhJyYHIuGWnhygufTX2e6xbyZDSjChJr4SXnlLWw5p11i9LmJkUVzdwxelemx6an0BMw-N2fFuH7WFb1qNrBiU9e9zY3GgBldUjHswvYECZWp0p8fdsFzGvfAs2JNV6ukgILcu5QJdrrLY6nNUqLsia6XvQ=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGAXG5GHU4Wz05MFuUfJV2-aQ2JjzK9vEvvaW_hsdHUnaK75t5bpYEmlZwcWgPTBbPZewG7tFsc-HdCpyAs2NPLtyLN4WxSmZKtSQJ9WOvVYK6ERA6xE_hLC_KmallaD3mk1PCFXdsbKimH0DvVMLhfca89ysv5_ziQ-_aE5rui5DFZ-dVYn_McNSukncM_CElzCNd9J9EwGFDThQOGtq14RZ4D3LdF4yiNbGUexwXDZEhsxirHAwk=
- Goにおける
USED
マクロの役割(C言語部分での使用):- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH3bqn1chHA4qA253zKNkDnIoRSqF1rXC-WskChlQ7hrAoSHBhO6NmZtMBNZVwKZQRHN7MZDYjhfh2L77fKvHX-6dJBxzHPczJ6tFT1Sycx84X_QuFsXv2T3COtIXuN2toM8pFonOYti2EqbTng-LmoeJDrFepG5xnAGYHGpNboRGpJr7k=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF6I8Fz-OGugYNhWXkFZ5waZLRtTsbHweQBGJvIjyK1-F8nO9ig6Z7RU_frfN_N-2MWnNjXAJA2o_isu_qyL52o8kJRKbU48-pofQfZExPAJ_CmGccenl1c8oIfC-U1k6Urr-kHrPX-UkzpLzDUxP8=