[インデックス 18872] ファイルの概要
このコミットは、Goランタイムのガベージコレクション(GC)に関連するsrc/pkg/runtime/mgc0.c
ファイルに対するものです。mgc0.c
は、Goランタイムの初期のGC実装の一部を担っていたC言語のソースファイルであり、特にバックグラウンドでのスイープ処理(bgsweep
)やファイナライザの実行(runfinq
)といった重要な機能を含んでいました。現在のGoランタイムでは、GCのコア部分はGo言語で再実装され、主にsrc/runtime/mgc.go
などのファイルに移行していますが、このコミットが作成された時点ではmgc0.c
がGCの重要なコンポーネントでした。
コミット
- コミットハッシュ:
fed5428c4aca483ceec8a6cdeac5c80098a30e64
- Author: Dmitriy Vyukov dvyukov@google.com
- Date: Fri Mar 14 23:32:12 2014 +0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fed5428c4aca483ceec8a6cdeac5c80098a30e64
元コミット内容
runtime: fix another race in bgsweep
It's possible that bgsweep constantly does not catch up for some reason,
in this case runfinq was not woken at all.
R=rsc
CC=golang-codereviews
https://golang.org/cl/75940043
変更の背景
このコミットは、Goランタイムのガベージコレクション(GC)における既知の競合状態(race condition)を修正することを目的としています。具体的には、バックグラウンドスイープ(bgsweep
)が何らかの理由でGCサイクルに追いつかない場合に、ファイナライザを処理するゴルーチン(runfinq
)が全く起動されない、またはウェイクアップされないという問題がありました。
GoのGCは、メモリの解放をバックグラウンドで行うことで、アプリケーションの実行を可能な限り中断しないように設計されています。bgsweep
は、GCのマークフェーズで到達不能と判断されたオブジェクトが占めるメモリを実際に解放する役割を担います。一方、ファイナライザは、オブジェクトがGCによって回収される直前に実行される特定の関数であり、ファイルディスクリプタやネットワーク接続などの非メモリリソースのクリーンアップによく使用されます。
問題は、bgsweep
が遅延し、次のGCサイクルが開始されてもまだ前のサイクルのスイープが完了していないような状況で発生しました。このような場合、runfinq
ゴルーチンを起動またはウェイクアップするロジックが適切にトリガーされず、結果としてファイナライザが実行されない可能性がありました。これは、リソースリークやアプリケーションの予期せぬ動作につながる重大な問題です。このコミットは、bgsweep
の進行状況に関わらず、ファイナライザが確実に処理されるようにするための修正を導入しています。
前提知識の解説
Goのガベージコレクション (GC)
GoのGCは、自動メモリ管理システムであり、プログラムが使用しなくなったメモリを自動的に回収します。GoのGCは以下の特徴を持ちます。
- 並行性 (Concurrent): GCの大部分の処理は、アプリケーションのゴルーチンと並行して実行されます。これにより、プログラムの実行が停止する「Stop-The-World (STW)」ポーズの時間を最小限に抑え、アプリケーションの応答性を向上させます。
- トライカラーマーク&スイープ (Tri-color Mark-Sweep): GoのGCは、トライカラーアルゴリズムに基づくマーク&スイープ方式を採用しています。
- マークフェーズ: GCは、プログラムから到達可能なオブジェクト(ライブオブジェクト)を特定します。オブジェクトは白(未マーク、潜在的なゴミ)、灰(マーク済みだが参照先が未スキャン)、黒(マーク済みで参照先もスキャン済み)の3色で分類されます。
- スイープフェーズ: マークフェーズの後に、到達不能と判断されたオブジェクトが占めるメモリを解放します。
- 非世代別 (Non-generational): 多くのGCシステムとは異なり、GoのGCはオブジェクトを世代別に分類しません(例:Young世代、Old世代)。すべてのオブジェクトは均等に扱われます。
- 非圧縮 (Non-compacting): GoのGCは、メモリ内のオブジェクトを移動させません。これにより、オブジェクトの移動と参照の更新に伴うオーバーヘッドが回避されますが、メモリの断片化が発生する可能性があります。
- ライトバリア (Write Barriers): 並行マークフェーズ中にオブジェクト参照の変更を追跡し、ライブオブジェクトが誤って回収されないようにするために使用されます。
bgsweep
(バックグラウンドスイープ)
bgsweep
は、Goランタイム内でバックグラウンドで動作するゴルーチンであり、GCのスイープフェーズを担当します。マークフェーズで到達不能とマークされたメモリ領域を実際に解放し、再利用可能な状態にします。bgsweep
は低優先度で実行され、runtime.Gosched()
を呼び出してCPU時間を他のゴルーチンに譲ることで、アプリケーションのパフォーマンスへの影響を最小限に抑えます。しかし、これによりbgsweep
がGCサイクルに追いつかない状況が発生する可能性もあります。
ファイナライザ (runtime.SetFinalizer
)
Goのファイナライザは、runtime.SetFinalizer
関数を使用してオブジェクトに関連付けられる関数です。GCがオブジェクトが到達不能であると判断した後、そのオブジェクトがメモリから回収される前に、関連付けられたファイナライザ関数が実行されます。ファイナライザは、主にファイルディスクリプタ、ネットワーク接続、C言語で割り当てられたメモリなど、Goのヒープ外のリソースをクリーンアップするための「安全ネット」として使用されます。
ファイナライザの実行は保証されず、いつ実行されるかも予測できません。プログラムが終了する前に実行されない可能性もあります。そのため、重要なリソース管理にはdefer
文やio.Closer
インターフェースのような明示的なクリーンアップメカニズムが推奨されます。
ファイナライザは、runfinq
というゴルーチンによって処理されます。runfinq
は、ファイナライザキューに積まれたファイナライザを順次実行する役割を担います。
技術的詳細
このコミットが修正しようとしている問題は、bgsweep
とrunfinq
の間の同期に関するものです。
従来のロジックでは、bgsweep
がスイープ処理を完了した後、またはGCが完了した後に、finq
(ファイナライザキュー)にファイナライザが存在する場合にのみrunfinq
ゴルーチンを起動またはウェイクアップしていました。
しかし、bgsweep
が何らかの理由(例えば、大量のメモリ解放が必要で時間がかかる、または他の高優先度ゴルーチンにCPU時間を奪われるなど)で遅延し、次のGCサイクルが開始されてもまだスイープが完了していない場合、bgsweep
はruntime.sweepone()
が-1
を返すまでループし続けます。このループ中にrunfinq
をウェイクアップする機会が失われる可能性がありました。
このコミットでは、この問題を解決するために以下の変更が導入されています。
wakefing
関数の導入: ファイナライザを処理するゴルーチン(runfinq
)を起動またはウェイクアップするための専用関数wakefing
が導入されました。この関数は、finq
にファイナライザが存在する場合に、fing
ゴルーチン(runfinq
を実行するゴルーチン)がまだ存在しない場合は新規作成し、既に存在して待機状態(fingwait
がtrue)であればウェイクアップします。bgsweep
内でのwakefing
の呼び出し:bgsweep
のループ内で、runtime.sweepone()
が呼び出されるたびに、現在のスイープ世代(runtime.mheap.sweepgen
)がsweep.lastsweepgen
と異なる場合にwakefing()
が呼び出されるようになりました。これは、bgsweep
がGCに追いついていない場合でも、少なくともGCサイクルごとに一度はrunfinq
を起動またはウェイクアップすることを保証します。bgsweep
のスイープループが完了した後(while(runtime·sweepone() != -1)
のループを抜けた後)にもwakefing()
が呼び出されるようになりました。これにより、スイープが完了した時点でファイナライザが確実に処理されるようになります。
runtime·gc
内でのwakefing
の呼び出し:- GCが完了した後、
ConcurrentSweep
がfalseの場合(つまり、並行スイープが有効でない場合)、従来の複雑なfinq
チェックとfing
の起動/ウェイクアップロジックがwakefing()
の呼び出しに置き換えられました。これにより、GC完了後にもファイナライザが確実に処理されるようになります。
- GCが完了した後、
これらの変更により、bgsweep
の進行状況やGCの完了状態に関わらず、ファイナライザが適切なタイミングで処理されることが保証され、競合状態が解消されます。特に、sweep.lastsweepgen
の導入は、bgsweep
が遅延している状況でも、GC世代の変更を検知してrunfinq
を定期的にウェイクアップする重要な役割を果たします。
コアとなるコードの変更箇所
src/pkg/runtime/mgc0.c
ファイルにおける変更点は以下の通りです。
-
wakefing
関数のプロトタイプ宣言の追加:static void wakefing(void);
-
MHeap
構造体にlastsweepgen
フィールドの追加:static struct { G* g; bool parked; uint32 lastsweepgen; // 追加 MSpan** spans; uint32 nspan; // ... } sweep;
-
bgsweep
関数内の変更:bgsweep(void) { for(;;) { while(runtime·sweepone() != -1) { gcstats.nbgsweep++; if(sweep.lastsweepgen != runtime·mheap.sweepgen) { // 追加 // If bgsweep does not catch up for any reason // (does not finish before next GC), // we still need to kick off runfinq at least once per GC. sweep.lastsweepgen = runtime·mheap.sweepgen; wakefing(); // 追加 } runtime·gosched(); } // kick off goroutine to run queued finalizers wakefing(); // 追加 runtime·lock(&gclock); // 削除されたコードブロック: finq != nil のチェックと fing の起動/ウェイクアップ if(!runtime·mheap.sweepdone) { // ... } // ... } }
-
runtime·gc
関数内の変更:runtime·gc(int32 force) { // ... // now that gc is done, kick off finalizer thread if needed if(!ConcurrentSweep) { // kick off goroutine to run queued finalizers wakefing(); // 変更: 従来の複雑なロジックを wakefing() に置き換え // give the queued finalizers, if any, a chance to run runtime·gosched(); } // ... }
-
wakefing
関数の新規追加:static void wakefing(void) { if(finq == nil) return; runtime·lock(&gclock); // kick off or wake up goroutine to run queued finalizers if(fing == nil) fing = runtime·newproc1(&runfinqv, nil, 0, 0, runtime·gc); else if(fingwait) { fingwait = 0; runtime·ready(fing); } runtime·unlock(&gclock); }
コアとなるコードの解説
このコミットの核心は、ファイナライザ処理の起動ロジックを一元化し、より堅牢にすることです。
-
wakefing
関数の導入:- この関数は、ファイナライザキュー
finq
にファイナライザが存在するかどうかを最初にチェックします。存在しない場合は何もせずに関数を終了します。 gclock
というグローバルロックを取得し、ファイナライザ関連の共有データへのアクセスを保護します。fing
(ファイナライザを実行するゴルーチンへのポインタ)がnil
の場合、つまりファイナライザゴルーチンがまだ起動されていない場合は、runtime·newproc1
を呼び出して新しいゴルーチンを起動し、runfinqv
関数(ファイナライザを実行する実際の関数)を実行させます。fing
がnil
でなく、かつfingwait
がtrue
の場合(ファイナライザゴルーチンが現在待機状態にある場合)、fingwait
をfalse
に設定し、runtime·ready(fing)
を呼び出してそのゴルーチンを再開可能状態にします。- 最後に
gclock
を解放します。 - この関数により、ファイナライザゴルーチンの起動とウェイクアップのロジックがカプセル化され、複数の場所から安全に呼び出せるようになりました。
- この関数は、ファイナライザキュー
-
bgsweep
関数内の変更:sweep.lastsweepgen
という新しいフィールドが導入され、現在のGCスイープ世代を追跡します。while(runtime·sweepone() != -1)
ループ内で、sweep.lastsweepgen
がruntime·mheap.sweepgen
(現在のヒープのスイープ世代)と異なる場合、つまり新しいGCサイクルが開始されたことを意味する場合に、sweep.lastsweepgen
を更新し、wakefing()
を呼び出します。この変更は非常に重要です。これにより、bgsweep
が前のGCサイクルのスイープに追いついていない状況でも、新しいGCサイクルが始まるたびにrunfinq
がウェイクアップされる機会が与えられます。これは、ファイナライザがGCサイクルごとに少なくとも一度は処理されることを保証するための「安全弁」として機能します。- スイープループが終了した後(
runtime·sweepone()
が-1
を返した後)にも、wakefing()
が呼び出されます。これは、スイープが完了した時点で、まだ処理されていないファイナライザがあれば確実に処理を開始させるためです。 - 従来の
finq != nil
のチェックとfing
の起動/ウェイクアップに関する複雑なコードブロックが削除され、wakefing()
の呼び出しに置き換えられました。これにより、コードが簡潔になり、ロジックの重複が解消されました。
-
runtime·gc
関数内の変更:- GCが完了した後、
ConcurrentSweep
が有効でない場合に、従来のファイナライザ起動ロジックが単純に**wakefing()
の呼び出しに置き換えられました**。これにより、GC完了後のファイナライザ処理の起動がより直接的かつ確実になります。
- GCが完了した後、
これらの変更により、Goランタイムは、bgsweep
の進行状況やGCのタイミングに依存することなく、ファイナライザが確実に処理されるようになりました。これは、リソースリークの防止と、ファイナライザに依存するアプリケーションの安定性向上に貢献します。
関連リンク
参考にした情報源リンク
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG2ufNsn-HNVajKuDz_GGPIZpBSBfpALDGCExIC5ZkJE5KUKdUUctjEX3LPvgH-vOCxR0vU13JGLtLtZ537n3-o2MFiJnqyMxTyBhOC1zYNkocSoiQQtvwkbmLWm1qCuAI9DRGXMZdHse29eUztNrmn8H92Od824Yszn9EHNgMH43YJX-S_lFAG5c5vsJA=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQELVo-X3hcUqdv4xeIgU5uEJI9AjOHD5wyluVo_YWUaIfqZ_2xFePMTUU2ZP8TeWYl5jW6fMPYqRmF9kJXvgAicTwHf9JDYiHb8wSysCMx_OVvIGnG6AQAbnShoTg33AVRBh85laGzem8jL5rs7IySJpY1rh2-inttC75ENtS5d4Y3w_cAOmYQyCWVqsw==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHqnuoZue6n1F_QEDaqKzi_H538stCgcQNSOKfYj-NKSp_bCbw-MMI1DYfGig18W0nG4wH-iljuhcVJyx87unSSpdvSk3DFmVOyAKuDLTzF1tyf_OeKrMsPVGWniPIS4cW4qki2BqMxpRROr5m21C8pK4aE9nMA8Z5aNf9c16M44rF4IgsMX6Q8m4vfwW855Qjg3vd1JKl8myb-t91YX83P0ZG2guHjrN6m0_Ad2oY0c7vn-RHp6tT8fZQNXbO-aPOMkLy2
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHLMy-1z_Yr06jmTExzMYe_51Un9NqqNwI_ovqvYKeWCt7nDtT7uLH5X23UBCJLLMfPSZRvsad31oo8jafvWXf1dk_2H9L7nqR5KRzHyYs4hEvMVEb_fGYhktRFn85Wt-8yjUsXH7YDPdH3PRvsFbWfRMBSNp1Ikd45mA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE-if_MzkkWrNwRQ_QqkD40xiCr0aywfQA91OnN_FSmrstqwfoaZ800nfvZEmyN4CyJiSg_iY4MJjf143quS3tMxgo_muo8hkxal0wW0hYTkVthPPHZr0DYmVFw2hNJm6IMz5ccJru9OxeQ4HPzwizhlnzD_BT_Ro83Bk8=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFXQit2QjW-ZEgmAcrIJfvn2Pt5_ki2WK9hOiMAetEiIZ29L5gbQdhy1NA_qaIddFKolv7RXkKMQk5Zg15Qz2J9olIiEVP1O4Xny_XGBro9oOwjKvmtfUZpeN0gP5Ruok-ZQj4WK8bWbC6wfdTGwzaInmXGRPIHkfYKx2vGXggfEYuAvTiivEQ2PtarAl1M_GA3ezmrWWv2wtiu9NsC6_os
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFMz1vCaQG_9V7tycABJ1XovnrWSLqiDMNq6vbJPRhfE0RrvgBuuvFGOm17fpCql3B3EOz6VeRu3M3LFu4EaXTiLk0gbFR06oGiD4XUDRFRbbqW0fy3MrF5CK7OwQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHhbUZM77O10nrGkJQqsXwnkWQoeVxiDygKsOGPfh9nb_UIBqMUIco2iVDCAQURp7Cf9dXfhbuiBHnYH0kIvruTfeDX0flAHK18cZ8QVtnJn5GVA9od2MakC2jaTo557U6ypSWaFFL1QfxCPbbC3yEpaWu1Cu2MY8_ePtngYUuCdkiJY-9Xi64JzEY=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFDzxirPHKOHWaDiz-1Ki7saTPJ34fVZJOINObmqQktiFmPluYfScEo0SXjvGbHMGIqSh0W7jJAPFpWjQy4-6oNWUDCIaCfNcvDhkoFpH_IUUqR6aDwjEzrgIELkbosQG8xz7L_moZa2vvaKCq_OOzu9Ds9p8Tg3IgCH_yk9wWGQJqbnNo=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEuCL8wmH-gzWYK3qepWx85L3wba6xdrFeDRAw-nCfER0D-qdXGVxQPp80vIQw5w1uALazb8OWd9UFufw_luaBOG5QsZJr9HJvizwghI3gtgPJfupbKoE1F1NmEP5tD5hU9NW4=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF-NgCb9kWXe037jXBW6P-CbKH07YLUfA1eQqtsNd4KeqHTTAuklG1ZMUtrhQAU1DVzvfgdbSJ79LKeEpqz9QnwEAPCYqEdleVGJmbqBjrD28Ha7pxI0fXQL5Sv94=