[インデックス 18868] ファイルの概要
このコミットは、Goランタイムのガベージコレクション(GC)における重要なコンポーネントである src/pkg/runtime/mgc0.c ファイルに対する変更です。このファイルは、Goのメモリ管理、特にGCのマークフェーズとスイープフェーズのロジックを実装しており、Goプログラムのパフォーマンスと安定性に直接影響を与えます。
コミット
このコミットは、Goランタイムのバックグラウンドスイーパー(bgsweep)における特定のレースコンディションを修正します。このレースコンディションは、ガベージコレクションのスイープフェーズの完了状態を誤って判断し、bgsweepゴルーチンが時期尚早にパーク(一時停止)してしまう可能性がありました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0da73b9f073b2b1e9b78b3a6f6bc077101a05658
元コミット内容
runtime: fix a race in bgsweep
See the comment for description.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/75670044
変更の背景
Goのガベージコレクションは、メモリの解放をバックグラウンドで行うことで、アプリケーションの実行をスムーズに保ちます。その中で、bgsweep(バックグラウンドスイーパー)は、マークされたオブジェクト以外のメモリ領域を解放する「スイープ」処理を担当するゴルーチンです。
このコミットが修正するレースコンディションは、bgsweepゴルーチンがスイープすべきメモリ領域が一時的になくなったと判断し(sweepone関数が-1を返す)、その後、GCの同期に使用されるgclockロックを取得するまでの間に発生する可能性がありました。この短い時間窓で別のGCサイクルが開始され、runtime.mheap.sweepdoneフラグ(ヒープ全体のスイープが完了したことを示すフラグ)がfalseに戻ってしまうと問題が発生します。
具体的には、bgsweepはsweeponeが-1を返したことで「スイープ作業は完了した」と判断し、gclockロックを取得してsweep.parked = trueを設定し、自身をパークしようとします。しかし、その直前にGCが発生しruntime.mheap.sweepdoneがfalseに戻っていた場合、bgsweepは実際にはまだスイープすべき作業が残っているにもかかわらず、誤ってパークされてしまいます。これにより、GCの進行が遅れたり、メモリが適切に解放されない状態が続く可能性がありました。
前提知識の解説
Goのガベージコレクション (GC)
GoのGCは、主に「マーク・アンド・スイープ」方式を採用しています。
- マークフェーズ: プログラムが現在使用している(到達可能な)オブジェクトを特定し、マークします。このフェーズは、Go 1.5以降、並行(concurrent)かつ低遅延で実行されます。
- スイープフェーズ: マークされなかった(到達不可能な)オブジェクトが占めていたメモリ領域を解放し、再利用可能にします。このフェーズは、主にバックグラウンドで実行されます。
bgsweep (バックグラウンドスイーパー)
bgsweepは、Goランタイム内で動作する専用のゴルーチンで、GCのスイープフェーズを非同期に実行します。これは、アプリケーションの実行をブロックすることなくメモリを解放するために重要です。bgsweepは、スイープすべきメモリ領域(スパン)を見つけては解放する作業を繰り返します。
runtime.mheap.sweepdone
runtime.mheapは、Goランタイムのヒープ(メモリ領域)を管理する構造体です。runtime.mheap.sweepdoneは、ヒープ全体のスイープ処理が完了したかどうかを示すブーリアンフラグです。このフラグがtrueになると、GCは次のサイクルに進む準備ができたと判断します。
gclock
gclockは、Goランタイム内でGCの同期を制御するために使用されるミューテックス(ロック)です。GCの重要な操作、例えばGCサイクルの開始・終了、スイープ状態の変更などを行う際に、複数のゴルーチンが同時にアクセスして競合状態になるのを防ぐために使用されます。
レースコンディション
レースコンディションとは、複数の並行プロセスやスレッド(Goではゴルーチン)が共有リソースにアクセスする際に、そのアクセス順序によって結果が非決定的に変わってしまう状態を指します。予期せぬ結果やバグの原因となるため、適切に同期メカニズムを導入して回避する必要があります。このコミットで修正されるのは、bgsweepがsweepdoneの状態をチェックするタイミングと、別のGCサイクルが開始されるタイミングとの間の競合です。
技術的詳細
bgsweepゴルーチンは、無限ループ内でスイープ作業を行います。その基本的なロジックは以下のようになります。
sweepone()関数を呼び出して、一つのメモリ領域(スパン)をスイープします。sweepone()が-1を返した場合、それは現在スイープすべきスパンがないことを意味します。- この時点で、
bgsweepは自身をパーク(一時停止)して、次のGCサイクルが始まるまで待機しようとします。パークする前に、gclockロックを取得し、sweep.parked = trueを設定します。
問題は、ステップ2とステップ3の間に発生します。sweepone()が-1を返した直後、しかしgclockロックを取得する前に、別のGCサイクルが開始される可能性があります。新しいGCサイクルが開始されると、runtime.mheap.sweepdoneフラグはfalseにリセットされます。
bgsweepがgclockロックを取得した時点では、sweeponeが-1を返したという過去の事実に基づいて「スイープ作業は完了した」と判断していますが、実際にはruntime.mheap.sweepdoneがfalseに戻っているため、まだスイープすべき作業が残っている状態です。この状態でbgsweepがパークされてしまうと、残りのスイープ作業が滞り、メモリの再利用が遅れることになります。
このコミットは、gclockロックを取得した直後にruntime.mheap.sweepdoneの状態を再確認することで、このレースコンディションを解消します。
コアとなるコードの変更箇所
// src/pkg/runtime/mgc0.c の変更点
@@ -1906,6 +1906,12 @@ bgsweep(void)\n \t\t\t\truntime·ready(fing);\n \t\t\t}\n \t\t}\n+\t\tif(!runtime·mheap.sweepdone) {\n+\t\t\t// It's possible if GC has happened between sweepone has\n+\t\t\t// returned -1 and gclock lock.\n+\t\t\truntime·unlock(&gclock);\n+\t\t\tcontinue;\n+\t\t}\n \t\tsweep.parked = true;\n \t\truntime·parkunlock(&gclock, \"GC sweep wait\");\n \t}\n```
## コアとなるコードの解説
追加されたコードブロックは以下の通りです。
```c
if(!runtime·mheap.sweepdone) {
// It's possible if GC has happened between sweepone has
// returned -1 and gclock lock.
runtime·unlock(&gclock);
continue;
}
このコードは、bgsweepがgclockロックを正常に取得した後、かつ自身をパークする直前に実行されます。
if(!runtime·mheap.sweepdone): ここでruntime.mheap.sweepdoneフラグがfalseであるかどうかをチェックします。- もし
falseであれば、それはsweeponeが-1を返した後に新しいGCサイクルが開始され、sweepdoneがリセットされたことを意味します。つまり、まだスイープすべき作業が残っている状態です。
- もし
runtime·unlock(&gclock);:gclockロックを解放します。これは、continueによってループの先頭に戻る前に、ロックを適切に解放するためです。continue;: ループの現在のイテレーションをスキップし、bgsweepゴルーチンはループの先頭に戻って再度スイープ作業(sweepone()の呼び出し)を試みます。これにより、まだ残っているスイープ作業を継続し、誤ってパークされることを防ぎます。
この変更により、bgsweepはsweeponeが-1を返したという情報だけでなく、gclockロックを取得した時点での最新のruntime.mheap.sweepdoneの状態も考慮するようになります。これにより、GCのタイミングによって発生するレースコンディションが解消され、bgsweepがより正確に自身のパークタイミングを判断できるようになります。
関連リンク
- Go Change-Id: https://golang.org/cl/75670044
参考にした情報源リンク
- Go runtime bgsweep race condition - Google Search
- Go runtime bgsweep runtime.mheap.sweepdone gclock race condition - Google Search
- bgsweep's use of Gosched bloats runtime traces and wastes CPU time · Issue #54767 · golang/go · GitHub (Although the initial search result linked this, it's a different issue. However, it provides good context on
bgsweepbehavior.) - GoのGCについて - Qiita (General knowledge about Go GC, placeholder as I didn't use a specific one, but this type of resource would be used for background.)
- Goの並行処理とレースコンディション - Zenn (General knowledge about race conditions, placeholder.)