[インデックス 18869] ファイルの概要
コミット
commit d8e6881166e280cc44056f1a6c9747a103dca340
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Fri Mar 14 21:22:03 2014 +0400
runtime: report "out of memory" in efence mode
Currently processes crash with obscure message.
Say that it's "out of memory".
LGTM=rsc
R=golang-codereviews
CC=golang-codereviews, khr, rsc
https://golang.org/cl/75820045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d8e6881166e280cc44056f1a6c9747a103dca340
元コミット内容
このコミットは、Goランタイムにおいて、efence
モードでメモリ割り当てが失敗した場合に、より明確な「out of memory」エラーを報告するように変更します。これまでは、不明瞭なメッセージでプロセスがクラッシュしていました。
変更の背景
Goランタイムには、メモリ関連のデバッグを支援するためのefence
(Electric Fence)モードが存在します。このモードは、メモリの不正アクセスやオーバーフローなどの問題を早期に検出するために、メモリ割り当ての境界にガードページを配置するなどの厳密なチェックを行います。
しかし、このefence
モードが有効な状態で、スタックの割り当て(runtime·stackalloc
関数)がシステムコール(runtime·SysAlloc
)によって失敗した場合、Goランタイムは具体的なエラーメッセージを出力せずにクラッシュしていました。これにより、開発者は何が問題なのかを特定するのが困難でした。
このコミットの目的は、このような状況で「out of memory」という明確なメッセージを報告することで、デバッグの労力を軽減し、問題の原因を特定しやすくすることにあります。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理する部分です。ガベージコレクション、スケジューリング、メモリ管理、スタック管理など、低レベルの操作を担当します。C言語で書かれた部分とGo言語で書かれた部分があります。
efence
モード (Electric Fence mode): Goランタイムのデバッグモードの一つで、メモリの不正アクセスを検出するために使用されます。環境変数GODEBUG=efence=1
を設定することで有効にできます。このモードでは、メモリ割り当ての際に、割り当てられた領域の直前と直後にアクセス不可能なページ(ガードページ)を配置し、これらのページへのアクセスを検出することで、バッファオーバーフローやアンダーフローといったメモリ破壊を早期に発見します。- スタック (Stack): プログラムの実行中に、関数呼び出しの引数、ローカル変数、リターンアドレスなどが一時的に格納されるメモリ領域です。Goのgoroutineはそれぞれ独自のスタックを持っています。
runtime·SysAlloc
: GoランタイムがOSから直接メモリを割り当てるための内部関数です。これは通常、大きなメモリブロックや、特定の特性を持つメモリ(例えば、ガードページを持つメモリ)を確保するために使用されます。runtime·throw
: Goランタイムの内部関数で、回復不可能な致命的なエラーが発生した場合に呼び出されます。この関数が呼び出されると、Goプログラムはパニックを起こし、スタックトレースを出力して終了します。ROUND(n, PageSize)
:n
をPageSize
の倍数に切り上げるマクロまたは関数です。メモリ割り当ては通常、OSのページサイズ(例えば4KB)の倍数で行われるため、要求されたサイズをページサイズに揃えるために使用されます。mstats.stacks_sys
: Goランタイムのメモリ統計情報の一部で、システムから割り当てられたスタックメモリの総量を追跡します。
技術的詳細
このコミットは、src/pkg/runtime/stack.c
ファイル内のruntime·stackalloc
関数に焦点を当てています。この関数は、goroutineのスタックを割り当てる責任を負っています。
変更前のコードでは、efence
モードが有効な場合(runtime·debug.efence
が真の場合)またはStackFromSystem
が真の場合(システムから直接スタックを割り当てる必要がある場合)、runtime·SysAlloc
を呼び出してメモリを確保していました。しかし、runtime·SysAlloc
がnil
(メモリ割り当て失敗)を返した場合のチェックがありませんでした。このため、メモリ割り当てが失敗すると、後続のメモリへのアクセスで不正なアドレスにアクセスしようとして、不明瞭なクラッシュが発生していました。
変更後のコードでは、runtime·SysAlloc
の戻り値をチェックする条件分岐が追加されています。具体的には、v = runtime·SysAlloc(...)
で割り当てを試みた後、if(v == nil)
という条件で割り当てが成功したかどうかを確認します。もしv
がnil
であれば、メモリ割り当てが失敗したことを意味するため、runtime·throw("out of memory (stackalloc)")
を呼び出して、より明確なエラーメッセージと共にプログラムを終了させます。
この変更により、efence
モードでのデバッグ時に、メモリ不足が原因でスタック割り当てが失敗した場合に、開発者が迅速に問題の原因を特定できるようになります。
コアとなるコードの変更箇所
src/pkg/runtime/stack.c
ファイルのruntime·stackalloc
関数内。
--- a/src/pkg/runtime/stack.c
+++ b/src/pkg/runtime/stack.c
@@ -102,8 +102,12 @@ runtime·stackalloc(G *gp, uint32 n)
runtime·printf("stackalloc %d\n", n);
gp->stacksize += n;
- if(runtime·debug.efence || StackFromSystem)
- return runtime·SysAlloc(ROUND(n, PageSize), &mstats.stacks_sys);
+ if(runtime·debug.efence || StackFromSystem) {
+ v = runtime·SysAlloc(ROUND(n, PageSize), &mstats.stacks_sys);
+ if(v == nil)
+ runtime·throw("out of memory (stackalloc)");
+ return v;
+ }
// Minimum-sized stacks are allocated with a fixed-size free-list allocator,
// but if we need a stack of a bigger size, we fall back on malloc
コアとなるコードの解説
変更の中心は、if(runtime·debug.efence || StackFromSystem)
ブロック内です。
-
変更前:
if(runtime·debug.efence || StackFromSystem) return runtime·SysAlloc(ROUND(n, PageSize), &mstats.stacks_sys);
ここでは、
runtime·SysAlloc
の戻り値が直接return
されていました。runtime·SysAlloc
がメモリ割り当てに失敗してnil
を返した場合、そのnil
がそのまま返され、呼び出し元でnil
ポインタに対する操作が行われ、クラッシュにつながっていました。 -
変更後:
if(runtime·debug.efence || StackFromSystem) { v = runtime·SysAlloc(ROUND(n, PageSize), &mstats.stacks_sys); if(v == nil) runtime·throw("out of memory (stackalloc)"); return v; }
v = runtime·SysAlloc(ROUND(n, PageSize), &mstats.stacks_sys);
: まず、runtime·SysAlloc
の戻り値を一時変数v
に格納します。if(v == nil)
:v
がnil
であるか(メモリ割り当てが失敗したか)をチェックします。runtime·throw("out of memory (stackalloc)");
: もしv
がnil
であれば、runtime·throw
を呼び出し、「out of memory (stackalloc)」という明確なエラーメッセージと共にプログラムを終了させます。これにより、デバッグ時に問題の原因がメモリ不足であることがすぐに分かります。return v;
: メモリ割り当てが成功した場合は、通常通りv
を返します。
この変更は、エラーハンドリングの改善であり、特にデバッグモードでのユーザーエクスペリエンスを向上させるものです。
関連リンク
- Goのメモリ管理に関するドキュメントやブログ記事:
- Go のメモリ管理 - Qiita (一般的なGoのメモリ管理について)
- Go のメモリ管理と GC の仕組み - Speaker Deck (Goのメモリ管理とGCの仕組みについて)
- Goの
efence
モードに関する情報:GODEBUG
環境変数に関するGoの公式ドキュメントやブログ記事。
参考にした情報源リンク
- golang/go のコミット履歴
- Go のソースコード (src/pkg/runtime/stack.c) (現在のGoのソースコードは
src/runtime/stack.go
に移動しています。コミット当時のパスはsrc/pkg/runtime/stack.c
でした。) - Go の
runtime.throw
関数に関する情報 (Goの公式ドキュメント) - Go の
SysAlloc
関数に関する情報 (Goの公式ドキュメント) - Go の
GODEBUG
環境変数に関する情報 (Goの公式ドキュメント) - Go の
efence
モードに関する議論やドキュメント (GoのGitHub Issues) - Go の
CL 75820045
(Change List) (Goのコードレビューシステムでの変更履歴)