Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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): nPageSizeの倍数に切り上げるマクロまたは関数です。メモリ割り当ては通常、OSのページサイズ(例えば4KB)の倍数で行われるため、要求されたサイズをページサイズに揃えるために使用されます。
  • mstats.stacks_sys: Goランタイムのメモリ統計情報の一部で、システムから割り当てられたスタックメモリの総量を追跡します。

技術的詳細

このコミットは、src/pkg/runtime/stack.cファイル内のruntime·stackalloc関数に焦点を当てています。この関数は、goroutineのスタックを割り当てる責任を負っています。

変更前のコードでは、efenceモードが有効な場合(runtime·debug.efenceが真の場合)またはStackFromSystemが真の場合(システムから直接スタックを割り当てる必要がある場合)、runtime·SysAllocを呼び出してメモリを確保していました。しかし、runtime·SysAllocnil(メモリ割り当て失敗)を返した場合のチェックがありませんでした。このため、メモリ割り当てが失敗すると、後続のメモリへのアクセスで不正なアドレスにアクセスしようとして、不明瞭なクラッシュが発生していました。

変更後のコードでは、runtime·SysAllocの戻り値をチェックする条件分岐が追加されています。具体的には、v = runtime·SysAlloc(...)で割り当てを試みた後、if(v == nil)という条件で割り当てが成功したかどうかを確認します。もしvnilであれば、メモリ割り当てが失敗したことを意味するため、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)ブロック内です。

  1. 変更前:

    if(runtime·debug.efence || StackFromSystem)
        return runtime·SysAlloc(ROUND(n, PageSize), &mstats.stacks_sys);
    

    ここでは、runtime·SysAllocの戻り値が直接returnされていました。runtime·SysAllocがメモリ割り当てに失敗してnilを返した場合、そのnilがそのまま返され、呼び出し元でnilポインタに対する操作が行われ、クラッシュにつながっていました。

  2. 変更後:

    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): vnilであるか(メモリ割り当てが失敗したか)をチェックします。
    • runtime·throw("out of memory (stackalloc)");: もしvnilであれば、runtime·throwを呼び出し、「out of memory (stackalloc)」という明確なエラーメッセージと共にプログラムを終了させます。これにより、デバッグ時に問題の原因がメモリ不足であることがすぐに分かります。
    • return v;: メモリ割り当てが成功した場合は、通常通りvを返します。

この変更は、エラーハンドリングの改善であり、特にデバッグモードでのユーザーエクスペリエンスを向上させるものです。

関連リンク

参考にした情報源リンク