[インデックス 18780] ファイルの概要
このコミットは、Goランタイムにおけるスタックコピー操作(copystack
)後に発生するゴルーチンの不正なステータス(g->status
)の問題を修正します。具体的には、スタックが拡張された際にゴルーチンのステータスが正しく復元されないことによって引き起こされる潜在的なバグに対処しています。
コミット
- コミットハッシュ:
8ca3372d7b63c8c61ea68daa9e3fc63213eb7965
- 作者: Dmitriy Vyukov dvyukov@google.com
- 日付: 2014年3月6日 木曜日 21:33:19 +0400
- コミットメッセージ:
runtime: fix bad g status after copystack
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8ca3372d7b63c8c61ea68daa9e3fc63213eb7965
元コミット内容
runtime: fix bad g status after copystack
LGTM=khr
R=khr
CC=golang-codereviews, rsc
https://golang.org/cl/69870054
変更の背景
Goランタイムは、ゴルーチンのスタックを動的に管理します。ゴルーチンがより多くのスタック領域を必要とする場合、ランタイムは既存のスタックをより大きな新しいスタックにコピーするcopystack
のような操作を実行します。このコミットが修正しようとしている問題は、copystack
操作が完了した後、ゴルーチンのステータス(g->status
)が正しく復元されない、または更新されないことによって発生していました。
ゴルーチンのステータスは、ランタイムスケジューラがゴルーチンをどのように扱うべきかを決定するために非常に重要です。例えば、Grunning
ステータスはゴルーチンが現在実行中であることを示し、Gwaiting
はI/O待ちなどでブロックされていることを示します。もしcopystack
後にステータスが不正なままになると、ランタイムがゴルーチンを誤って処理し、デッドロック、パニック、またはその他の予期せぬ動作を引き起こす可能性がありました。
このコミットは、runtime·park
、runtime·gosched
、runtime·goexit
といった、ゴルーチンのステータスに依存する重要な関数に、g->status
がGrunning
であることを確認するチェックを追加することで、この問題を早期に検出できるようにしています。そして、copystack
後にoldstatus
をgp->status
に明示的に設定することで、根本的な原因を修正しています。
前提知識の解説
Goランタイム
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション、ゴルーチンとスケジューリング、メモリ管理、チャネル通信などが含まれます。Goプログラムは、オペレーティングシステム上で直接実行されるのではなく、Goランタイム上で実行されます。
ゴルーチン (g
)
ゴルーチンはGoにおける軽量な実行スレッドです。OSのスレッドよりもはるかに軽量であり、数百万のゴルーチンを同時に実行することも可能です。各ゴルーチンはg
構造体によって表現され、その状態やスタック情報などが含まれます。
ゴルーチンのステータス (g->status
)
g
構造体には、ゴルーチンの現在の状態を示すstatus
フィールドがあります。主なステータスには以下のようなものがあります。
Grunning
: ゴルーチンが現在実行中であるか、実行可能キューに入っている状態。Gwaiting
: ゴルーチンが何らかのイベント(I/O、チャネル操作、タイマーなど)を待っている状態。Gsyscall
: ゴルーチンがシステムコールを実行中である状態。Gdead
: ゴルーチンが終了した状態。
これらのステータスは、Goスケジューラがゴルーチンを効率的に管理し、適切なタイミングで実行を切り替えるために不可欠です。
スタック管理とcopystack
Goのゴルーチンは、最初は小さなスタック(通常は数KB)で開始されます。関数呼び出しが深くなったり、大きなローカル変数が使用されたりしてスタックが不足しそうになると、Goランタイムは自動的にスタックを拡張します。この拡張プロセスは、通常、現在のスタックの内容をより大きな新しいスタック領域にコピーすることによって行われます。このコピー操作を行う関数の一つがcopystack
です。
runtime·throw
runtime·throw
は、Goランタイム内部で回復不能なエラーが発生した場合に呼び出される関数です。これはプログラムを即座に終了させ、スタックトレースを出力します。通常、これはランタイムの内部状態が矛盾していることを示しており、プログラムの続行が不可能であることを意味します。
runtime·park
, runtime·gosched
, runtime·goexit
これらはGoランタイムの重要な関数で、ゴルーチンのスケジューリングやライフサイクルに関連しています。
runtime·park
: 現在のゴルーチンを一時停止させ、指定された条件が満たされるまで待機状態にします。runtime·gosched
: 現在のゴルーチンを実行可能状態に戻し、スケジューラに制御を渡し、別のゴルーチンを実行させます。これは明示的な協調的スケジューリングのポイントです。runtime·goexit
: 現在のゴルーチンを終了させます。
これらの関数は、ゴルーチンのステータスがGrunning
であることを前提として動作することが多いため、不正なステータスで呼び出されると問題が発生します。
技術的詳細
このコミットは、copystack
操作後にゴルーチンのステータスが正しくない状態になるという特定のバグを修正します。
問題の根源は、runtime·newstack
関数内にありました。runtime·newstack
は、ゴルーチンのスタックが不足した際に新しいスタックを割り当て、古いスタックの内容を新しいスタックにコピーする(copystack
を呼び出す)役割を担っています。しかし、copystack
が完了した後、ゴルーチンのステータスが、スタックコピー操作に入る前の元のステータスに正しく復元されていませんでした。これにより、ゴルーチンがGrunning
ではないにもかかわらず、Grunning
を期待するruntime·park
、runtime·gosched
、runtime·goexit
のような関数が呼び出される可能性がありました。
このコミットは2つの主要な変更を導入しています。
-
早期検出のためのチェックの追加:
src/pkg/runtime/proc.c
内のruntime·park
、runtime·gosched
、runtime·goexit
関数に、現在のゴルーチンg
のステータスがGrunning
であることを確認するif(g->status != Grunning) runtime·throw("bad g status");
というチェックを追加しました。これにより、不正なステータスでこれらの関数が呼び出された場合に、即座にruntime·throw
が実行され、問題が早期に診断されるようになります。これは、根本的な原因を修正する前に、問題の発生を明確にするための防御的なプログラミングです。 -
copystack
後のステータス復元:src/pkg/runtime/stack.c
内のruntime·newstack
関数において、copystack
が完了した直後にgp->status = oldstatus;
という行を追加しました。oldstatus
はcopystack
が呼び出される前のゴルーチンのステータスを保持しています。この変更により、スタックコピー操作が完了した後、ゴルーチンのステータスが元の正しい状態に確実に復元されるようになります。これにより、runtime·park
などの関数が不正なステータスで呼び出される根本原因が解消されます。
これらの変更により、Goランタイムの安定性と堅牢性が向上し、スタック拡張時のゴルーチンステータスの不整合に起因する潜在的なバグが排除されます。
コアとなるコードの変更箇所
src/pkg/runtime/proc.c
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -1364,6 +1364,8 @@ top:
void
runtime·park(bool(*unlockf)(G*, void*), void *lock, int8 *reason)
{
+ if(g->status != Grunning)
+ runtime·throw("bad g status");
m->waitlock = lock;
m->waitunlockf = unlockf;
g->waitreason = reason;
@@ -1415,6 +1417,8 @@ park0(G *gp)
void
runtime·gosched(void)
{
+ if(g->status != Grunning)
+ runtime·throw("bad g status");
runtime·mcall(runtime·gosched0);
}
@@ -1443,6 +1447,8 @@ runtime·gosched0(G *gp)
void
runtime·goexit(void)
{
+ if(g->status != Grunning)
+ runtime·throw("bad g status");
if(raceenabled)
runtime·racegoend();
runtime·mcall(goexit0);
src/pkg/runtime/stack.c
--- a/src/pkg/runtime/stack.c
+++ b/src/pkg/runtime/stack.c
@@ -640,6 +640,7 @@ runtime·newstack(void)\n copystack(gp, nframes, newsize);\n if(StackDebug >= 1)\n runtime·printf("stack grow done\\n");\n+\t\t\tgp->status = oldstatus;\n runtime·gogo(&gp->sched);\n }\n // TODO: if stack is uncopyable because we're in C code, patch return value at\n```
## コアとなるコードの解説
### `src/pkg/runtime/proc.c` の変更
`runtime·park`、`runtime·gosched`、`runtime·goexit`の各関数の冒頭に、`if(g->status != Grunning) runtime·throw("bad g status");`というチェックが追加されました。
* **`g->status != Grunning`**: これは、現在のゴルーチン`g`のステータスが`Grunning`(実行中または実行可能)ではない場合に真となります。
* **`runtime·throw("bad g status")`**: 上記の条件が真の場合、つまりゴルーチンが`Grunning`ではない状態でこれらの関数が呼び出された場合、ランタイムは致命的なエラーを発生させ、プログラムを終了させます。
この変更は、スタックコピー後にゴルーチンのステータスが不正なままになっているという問題を直接修正するものではありませんが、その問題が引き起こす可能性のある不正な状態遷移を早期に検出し、デバッグを容易にするための防御的な措置です。これにより、ランタイムの内部的な不整合がより明確になります。
### `src/pkg/runtime/stack.c` の変更
`runtime·newstack`関数内の`copystack`呼び出しの直後に、`gp->status = oldstatus;`という行が追加されました。
* **`copystack(gp, nframes, newsize);`**: この行は、ゴルーチン`gp`のスタックを新しいサイズ`newsize`のスタックにコピーする操作を実行します。
* **`gp->status = oldstatus;`**: この行が追加されたことで、`copystack`によるスタックの再配置が完了した後、ゴルーチン`gp`のステータスが、スタック拡張処理に入る前の元のステータス(`oldstatus`に保存されていた値)に明示的に設定されるようになりました。
この変更がこのコミットの核心的な修正です。これにより、スタックコピー操作中に一時的に変更されたり、あるいは単に復元されなかったりしたゴルーチンのステータスが、`copystack`完了後に確実に正しい状態に戻るようになります。結果として、`runtime·park`などの関数が、ゴルーチンが`Grunning`であるべきときに`Grunning`以外のステータスであるという状況が解消され、ランタイムの動作が安定します。
## 関連リンク
* Go CL 69870054: [https://golang.org/cl/69870054](https://golang.org/cl/69870054)
## 参考にした情報源リンク
* Goのソースコード(特に`src/pkg/runtime/proc.c`と`src/pkg/runtime/stack.c`)
* Goのスタック管理に関するドキュメントやブログ記事(一般的なGoランタイムの動作理解のため)
* Goのゴルーチンとスケジューラに関する情報(一般的なGoランタイムの動作理解のため)
* Goの`runtime·throw`に関する情報(エラーハンドリングの理解のため)
* Goの`g`構造体と`status`フィールドに関する情報(ゴルーチン状態の理解のため)# [インデックス 18780] ファイルの概要
このコミットは、Goランタイムにおけるスタックコピー操作(`copystack`)後に発生するゴルーチンの不正なステータス(`g->status`)の問題を修正します。具体的には、スタックが拡張された際にゴルーチンのステータスが正しく復元されないことによって引き起こされる潜在的なバグに対処しています。
## コミット
- **コミットハッシュ**: `8ca3372d7b63c8c61ea68daa9e3fc63213eb7965`
- **作者**: Dmitriy Vyukov <dvyukov@google.com>
- **日付**: 2014年3月6日 木曜日 21:33:19 +0400
- **コミットメッセージ**: `runtime: fix bad g status after copystack`
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/8ca3372d7b63c8c61ea68daa9e3fc63213eb7965](https://github.com/golang/go/commit/8ca3372d7b63c8c61ea68daa9e3fc63213eb7965)
## 元コミット内容
runtime: fix bad g status after copystack
LGTM=khr R=khr CC=golang-codereviews, rsc https://golang.org/cl/69870054
## 変更の背景
Goランタイムは、ゴルーチンのスタックを動的に管理します。ゴルーチンがより多くのスタック領域を必要とする場合、ランタイムは既存のスタックをより大きな新しいスタックにコピーする`copystack`のような操作を実行します。このコミットが修正しようとしている問題は、`copystack`操作が完了した後、ゴルーチンのステータス(`g->status`)が正しく復元されない、または更新されないことによって発生していました。
ゴルーチンのステータスは、ランタイムスケジューラがゴルーチンをどのように扱うべきかを決定するために非常に重要です。例えば、`Grunning`ステータスはゴルーチンが現在実行中であることを示し、`Gwaiting`はI/O待ちなどでブロックされていることを示します。もし`copystack`後にステータスが不正なままになると、ランタイムがゴルーチンを誤って処理し、デッドロック、パニック、またはその他の予期せぬ動作を引き起こす可能性がありました。
このコミットは、`runtime·park`、`runtime·gosched`、`runtime·goexit`といった、ゴルーチンのステータスに依存する重要な関数に、`g->status`が`Grunning`であることを確認するチェックを追加することで、この問題を早期に検出できるようにしています。そして、`copystack`後に`oldstatus`を`gp->status`に明示的に設定することで、根本的な原因を修正しています。
## 前提知識の解説
### Goランタイム
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション、ゴルーチンとスケジューリング、メモリ管理、チャネル通信などが含まれます。Goプログラムは、オペレーティングシステム上で直接実行されるのではなく、Goランタイム上で実行されます。
### ゴルーチン (`g`)
ゴルーチンはGoにおける軽量な実行スレッドです。OSのスレッドよりもはるかに軽量であり、数百万のゴルーチンを同時に実行することも可能です。各ゴルーチンは`g`構造体によって表現され、その状態やスタック情報などが含まれます。
### ゴルーチンのステータス (`g->status`)
`g`構造体には、ゴルーチンの現在の状態を示す`status`フィールドがあります。主なステータスには以下のようなものがあります。
* **`Grunning`**: ゴルーチンが現在実行中であるか、実行可能キューに入っている状態。
* **`Gwaiting`**: ゴルーチンが何らかのイベント(I/O、チャネル操作、タイマーなど)を待っている状態。
* **`Gsyscall`**: ゴルーチンがシステムコールを実行中である状態。
* **`Gdead`**: ゴルーチンが終了した状態。
これらのステータスは、Goスケジューラがゴルーチンを効率的に管理し、適切なタイミングで実行を切り替えるために不可欠です。
### スタック管理と`copystack`
Goのゴルーチンは、最初は小さなスタック(通常は数KB)で開始されます。関数呼び出しが深くなったり、大きなローカル変数が使用されたりしてスタックが不足しそうになると、Goランタイムは自動的にスタックを拡張します。この拡張プロセスは、通常、現在のスタックの内容をより大きな新しいスタック領域にコピーすることによって行われます。このコピー操作を行う関数の一つが`copystack`です。
### `runtime·throw`
`runtime·throw`は、Goランタイム内部で回復不能なエラーが発生した場合に呼び出される関数です。これはプログラムを即座に終了させ、スタックトレースを出力します。通常、これはランタイムの内部状態が矛盾していることを示しており、プログラムの続行が不可能であることを意味します。
### `runtime·park`, `runtime·gosched`, `runtime·goexit`
これらはGoランタイムの重要な関数で、ゴルーチンのスケジューリングやライフサイクルに関連しています。
* **`runtime·park`**: 現在のゴルーチンを一時停止させ、指定された条件が満たされるまで待機状態にします。
* **`runtime·gosched`**: 現在のゴルーチンを実行可能状態に戻し、スケジューラに制御を渡し、別のゴルーチンを実行させます。これは明示的な協調的スケジューリングのポイントです。
* **`runtime·goexit`**: 現在のゴルーチンを終了させます。
これらの関数は、ゴルーチンのステータスが`Grunning`であることを前提として動作することが多いため、不正なステータスで呼び出されると問題が発生します。
## 技術的詳細
このコミットは、`copystack`操作後にゴルーチンのステータスが正しくない状態になるという特定のバグを修正します。
問題の根源は、`runtime·newstack`関数内にありました。`runtime·newstack`は、ゴルーチンのスタックが不足した際に新しいスタックを割り当て、古いスタックの内容を新しいスタックにコピーする(`copystack`を呼び出す)役割を担っています。しかし、`copystack`が完了した後、ゴルーチンのステータスが、スタックコピー操作に入る前の元のステータスに正しく復元されていませんでした。これにより、ゴルーチンが`Grunning`ではないにもかかわらず、`Grunning`を期待する`runtime·park`、`runtime·gosched`、`runtime·goexit`のような関数が呼び出される可能性がありました。
このコミットは2つの主要な変更を導入しています。
1. **早期検出のためのチェックの追加**: `src/pkg/runtime/proc.c`内の`runtime·park`、`runtime·gosched`、`runtime·goexit`関数に、現在のゴルーチン`g`のステータスが`Grunning`であることを確認する`if(g->status != Grunning) runtime·throw("bad g status");`というチェックを追加しました。これにより、不正なステータスでこれらの関数が呼び出された場合に、即座に`runtime·throw`が実行され、問題が早期に診断されるようになります。これは、根本的な原因を修正する前に、問題の発生を明確にするための防御的なプログラミングです。
2. **`copystack`後のステータス復元**: `src/pkg/runtime/stack.c`内の`runtime·newstack`関数において、`copystack`が完了した直後に`gp->status = oldstatus;`という行を追加しました。`oldstatus`は`copystack`が呼び出される前のゴルーチンのステータスを保持しています。この変更により、スタックコピー操作が完了した後、ゴルーチンのステータスが元の正しい状態に確実に復元されるようになります。これにより、`runtime·park`などの関数が不正なステータスで呼び出される根本原因が解消されます。
これらの変更により、Goランタイムの安定性と堅牢性が向上し、スタック拡張時のゴルーチンステータスの不整合に起因する潜在的なバグが排除されます。
## コアとなるコードの変更箇所
### `src/pkg/runtime/proc.c`
```diff
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -1364,6 +1364,8 @@ top:
void
runtime·park(bool(*unlockf)(G*, void*), void *lock, int8 *reason)
{
+ if(g->status != Grunning)
+ runtime·throw("bad g status");
m->waitlock = lock;
m->waitunlockf = unlockf;
g->waitreason = reason;
@@ -1415,6 +1417,8 @@ park0(G *gp)
void
runtime·gosched(void)
{
+ if(g->status != Grunning)
+ runtime·throw("bad g status");
runtime·mcall(runtime·gosched0);
}
@@ -1443,6 +1447,8 @@ runtime·gosched0(G *gp)
void
runtime·goexit(void)
{
+ if(g->status != Grunning)
+ runtime·throw("bad g status");
if(raceenabled)
runtime·racegoend();
runtime·mcall(goexit0);
src/pkg/runtime/stack.c
--- a/src/pkg/runtime/stack.c
+++ b/src/pkg/runtime/stack.c
@@ -640,6 +640,7 @@ runtime·newstack(void)\n copystack(gp, nframes, newsize);\n if(StackDebug >= 1)\n runtime·printf("stack grow done\\n");\n+\t\t\tgp->status = oldstatus;\n runtime·gogo(&gp->sched);\n }\n // TODO: if stack is uncopyable because we're in C code, patch return value at\n```
## コアとなるコードの解説
### `src/pkg/runtime/proc.c` の変更
`runtime·park`、`runtime·gosched`、`runtime·goexit`の各関数の冒頭に、`if(g->status != Grunning) runtime·throw("bad g status");`というチェックが追加されました。
* **`g->status != Grunning`**: これは、現在のゴルーチン`g`のステータスが`Grunning`(実行中または実行可能)ではない場合に真となります。
* **`runtime·throw("bad g status")`**: 上記の条件が真の場合、つまりゴルーチンが`Grunning`ではない状態でこれらの関数が呼び出された場合、ランタイムは致命的なエラーを発生させ、プログラムを終了させます。
この変更は、スタックコピー後にゴルーチンのステータスが不正なままになっているという問題を直接修正するものではありませんが、その問題が引き起こす可能性のある不正な状態遷移を早期に検出し、デバッグを容易にするための防御的な措置です。これにより、ランタイムの内部的な不整合がより明確になります。
### `src/pkg/runtime/stack.c` の変更
`runtime·newstack`関数内の`copystack`呼び出しの直後に、`gp->status = oldstatus;`という行が追加されました。
* **`copystack(gp, nframes, newsize);`**: この行は、ゴルーチン`gp`のスタックを新しいサイズ`newsize`のスタックにコピーする操作を実行します。
* **`gp->status = oldstatus;`**: この行が追加されたことで、`copystack`によるスタックの再配置が完了した後、ゴルーチン`gp`のステータスが、スタック拡張処理に入る前の元のステータス(`oldstatus`に保存されていた値)に明示的に設定されるようになりました。
この変更がこのコミットの核心的な修正です。これにより、スタックコピー操作中に一時的に変更されたり、あるいは単に復元されなかったりしたゴルーチンのステータスが、`copystack`完了後に確実に正しい状態に戻るようになります。結果として、`runtime·park`などの関数が、ゴルーチンが`Grunning`であるべきときに`Grunning`以外のステータスであるという状況が解消され、ランタイムの動作が安定します。
## 関連リンク
* Go CL 69870054: [https://golang.org/cl/69870054](https://golang.org/cl/69870054)
## 参考にした情報源リンク
* Goのソースコード(特に`src/pkg/runtime/proc.c`と`src/pkg/runtime/stack.c`)
* Goのスタック管理に関するドキュメントやブログ記事(一般的なGoランタイムの動作理解のため)
* Goのゴルーチンとスケジューラに関する情報(一般的なGoランタイムの動作理解のため)
* Goの`runtime·throw`に関する情報(エラーハンドリングの理解のため)
* Goの`g`構造体と`status`フィールドに関する情報(ゴルーチン状態の理解のため)