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

[インデックス 18963] ファイルの概要

このコミットは、Goランタイムにおけるメモリリークの修正に関するものです。具体的には、m->moreargp および m->morebuf というフィールドが、ゴルーチンのプリエンプション(横取り)やスタック拡張の際に適切にクリアされなかったために発生していた、誤ったメモリ保持(false retention)を解消することを目的としています。これにより、大きなメモリブロックが永続的にリークする可能性があり、sync.Pool のファイナライザが失敗する問題を引き起こしていました。

コミット

runtime: eliminate false retention due to m->moreargp/morebuf
m->moreargp/morebuf were not cleared in case of preemption and stack growing,
it can lead to persistent leaks of large memory blocks.

It seems to fix the sync.Pool finalizer failures. I've run the test 500'000 times
w/o a single failure; previously it would fail dozens of times.

Fixes #7633.
Fixes #7533.

LGTM=rsc
R=golang-codereviews
CC=golang-codereviews, khr, rsc
https://golang.org/cl/80480044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/7c75a862b4df306a2573d1aa966d68803bc8b4e7

元コミット内容

runtime: eliminate false retention due to m->moreargp/morebuf
m->moreargp/morebuf were not cleared in case of preemption and stack growing,
it can lead to persistent leaks of large memory blocks.

It seems to fix the sync.Pool finalizer failures. I've run the test 500'000 times
w/o a single failure; previously it would fail dozens of times.

Fixes #7633.
Fixes #7533.

LGTM=rsc
R=golang-codereviews
CC=golang-codereviews, khr, rsc
https://golang.org/cl/80480044

変更の背景

このコミットの背景には、Goランタイムにおける特定のメモリリーク問題と、それに起因するsync.Poolのファイナライザの失敗がありました。

Goランタイムは、ゴルーチン(Goの軽量スレッド)のスタックを動的に管理します。ゴルーチンが関数呼び出しを深くネストしたり、大きなローカル変数を使用したりすると、スタックが不足する可能性があります。このような場合、ランタイムは自動的にスタックを拡張(stack growing)します。また、Goランタイムは協調的プリエンプション(cooperative preemption)を導入しており、長時間実行されるゴルーチンが他のゴルーチンにCPUを明け渡す機会を提供します。

問題は、これらのスタック拡張やプリエンプションのメカニズムと、m(machine)構造体内のmoreargpおよびmorebufというフィールドの管理方法にありました。m構造体は、OSスレッド(M)とゴルーチン(G)のスケジューリングに関連する情報を保持しています。moreargpmorebufは、スタック拡張やプリエンプションの際に、現在のゴルーチンの実行コンテキスト(特に引数ポインタやレジスタの状態)を一時的に保存するために使用されるフィールドでした。

しかし、これらのフィールドが、スタック拡張やプリエンプションが完了した後に適切にクリアされなかったため、古いスタックフレームや関連するメモリ領域への参照が残り続けてしまう「誤った保持(false retention)」が発生していました。ガベージコレクタは、これらの参照が存在する限り、参照されているメモリを解放できないため、結果としてメモリリークが発生していました。

このメモリリークは、特にsync.Poolのファイナライザの失敗という形で顕在化しました。sync.Poolは、オブジェクトの再利用を目的としたGoの標準ライブラリの機能です。sync.Poolに格納されたオブジェクトがガベージコレクタによって回収される際に、ファイナライザが実行されることがあります。メモリリークによってオブジェクトが適切に回収されないと、ファイナライザが期待通りに実行されず、テストの失敗やアプリケーションの不安定性につながっていました。コミットメッセージにあるように、この修正前はsync.Poolのテストが頻繁に失敗していたことが、問題の深刻さを示しています。

前提知識の解説

このコミットを理解するためには、以下のGoランタイムの概念を理解しておく必要があります。

  1. ゴルーチン (Goroutine): Go言語の軽量な実行単位です。OSスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。Goランタイムがゴルーチンのスケジューリング、スタック管理、およびプリエンプションを担当します。

  2. M (Machine) と G (Goroutine) と P (Processor): Goランタイムのスケジューラにおける主要な抽象化です。

    • M (Machine): OSスレッドを表します。Goのコードを実行する実際のOSスレッドです。
    • G (Goroutine): ゴルーチンを表します。
    • P (Processor): 論理プロセッサを表します。MがGを実行するためのコンテキストを提供します。PはMにアタッチされ、MはP上でGを実行します。
  3. スタック管理 (Stack Management): Goのゴルーチンは、最初は小さなスタック(通常は数KB)で開始します。関数呼び出しが深くなったり、大きなローカル変数が割り当てられたりしてスタックが不足しそうになると、Goランタイムは自動的にスタックを拡張します(stack growing)。これは、より大きな新しいスタックを割り当て、古いスタックの内容を新しいスタックにコピーし、ゴルーチンのスタックポインタを更新することで行われます。

  4. プリエンプション (Preemption): Goランタイムは、長時間実行されるゴルーチンが他のゴルーチンにCPUを明け渡すように、協調的プリエンプションを実装しています。これは、関数プロローグ(関数の開始部分)でスタックガードページをチェックすることで実現されます。スタックガードページに到達すると、ランタイムはゴルーチンの実行を一時停止し、スケジューラに制御を戻します。

  5. m 構造体: runtime.m 構造体は、各OSスレッド(M)の状態を保持します。これには、現在実行中のゴルーチン、スタック情報、スケジューリング関連のデータなどが含まれます。

  6. Gobuf 構造体: runtime.Gobuf 構造体は、ゴルーチンの実行コンテキスト(プログラムカウンタ pc、スタックポインタ sp、リンクレジスタ lr など)を保存するために使用されます。これは、ゴルーチンの切り替えや、スタック拡張、プリエンプションの際に、ゴルーチンの状態を保存・復元するために利用されます。

  7. m->moreargpm->morebuf: これらはm構造体内のフィールドで、スタック拡張やプリエンプションの際に一時的にゴルーチンの状態を保存するために使用されます。

    • m->moreargp: スタック拡張やプリエンプションの際に、呼び出し元の関数の引数へのポインタを一時的に保持するために使用されます。
    • m->morebuf: スタック拡張やプリエンプションの際に、Gobuf構造体として、ゴルーチンの実行コンテキスト(pc, sp, lrなど)を一時的に保持するために使用されます。
  8. sync.Pool: Goの標準ライブラリsyncパッケージに含まれる型で、一時的なオブジェクトの再利用を効率的に行うためのプールです。オブジェクトの生成コストが高い場合などに、オブジェクトをプールしておき、必要に応じて取り出し、使い終わったらプールに戻すことで、ガベージコレクションの負荷を軽減し、パフォーマンスを向上させます。sync.Poolに格納されたオブジェクトは、ガベージコレクションの対象となり、プールが不要になったオブジェクトを自動的に破棄するためにファイナライザが使用されることがあります。

技術的詳細

このコミットが修正する問題は、Goランタイムのruntime·newstack関数にありました。この関数は、ゴルーチンのスタックが不足した際に新しいスタックを割り当て、古いスタックの内容をコピーし、ゴルーチンの実行を新しいスタックに切り替える役割を担っています。また、プリエンプションが発生した場合も、この関数が呼び出され、ゴルーチンのコンテキストが保存されます。

問題の核心は、m->moreargpm->morebufというm構造体のフィールドが、スタック拡張やプリエンプションの処理中に一時的にゴルーチンの状態を保持するために使用されるにもかかわらず、その処理が完了した後にこれらのフィールドが適切にクリア(nilやゼロ値に設定)されていなかった点です。

具体的には、runtime·newstack関数内で、現在のゴルーチンの引数ポインタとGobufコンテキストがm->moreargpm->morebufに保存されます。その後、新しいスタックが設定され、ゴルーチンの実行が再開されます。しかし、この再開後もm->moreargpm->morebufには古いスタックフレームやコンテキストへの参照が残ったままでした。

ガベージコレクタは、到達可能なすべてのオブジェクトをマークし、マークされていないオブジェクトを解放します。m->moreargpm->morebufが古いスタックフレームへの参照を保持していると、たとえそのスタックフレームが論理的には不要になっていたとしても、ガベージコレクタはそれを到達可能と判断し、解放しませんでした。これが「誤った保持(false retention)」であり、結果としてメモリリークを引き起こしていました。

このメモリリークは、特にsync.Poolのファイナライザの失敗という形で表面化しました。sync.Poolは、オブジェクトを再利用するために、内部的にガベージコレクタと連携します。プール内のオブジェクトが不要になり、ガベージコレクタによって回収される際に、そのオブジェクトに設定されたファイナライザが実行されることがあります。しかし、m->moreargpm->morebufによる誤った参照が原因で、sync.Poolが管理するオブジェクトが期待通りに回収されず、ファイナライザが実行されない、あるいは遅延して実行されるといった問題が発生していました。これにより、sync.Poolのテストが不安定になり、頻繁に失敗する原因となっていました。

このコミットでは、runtime·newstack関数内で、m->moreargpm->morebufの値を一時変数に退避させた後、すぐにm構造体内のこれらのフィールドをnilまたはゼロ値にクリアするように変更しています。これにより、古いスタックフレームへの参照が永続的に保持されることがなくなり、ガベージコレクタが不要になったメモリを適切に解放できるようになりました。結果として、メモリリークが解消され、sync.Poolのファイナライザが期待通りに動作するようになり、関連するテストの失敗が解消されました。

コアとなるコードの変更箇所

変更は src/pkg/runtime/stack.c ファイルの runtime·newstack 関数内で行われています。

--- a/src/pkg/runtime/stack.c
+++ b/src/pkg/runtime/stack.c
@@ -608,7 +608,8 @@ runtime·newstack(void)
 	uintptr sp;
 	uintptr *src, *dst, *dstend;
 	G *gp;
-	Gobuf label;
+	Gobuf label, morebuf;
+	void *moreargp;
 	bool newstackcall;
 
 	if(m->forkstackguard)
@@ -627,6 +628,12 @@ runtime·newstack(void)
 
 	framesize = m->moreframesize;
 	argsize = m->moreargsize;
+	moreargp = m->moreargp;
+	m->moreargp = nil;
+	morebuf = m->morebuf;
+	m->morebuf.pc = (uintptr)nil;
+	m->morebuf.lr = (uintptr)nil;
+	m->morebuf.sp = (uintptr)nil;
 	gp->status = Gwaiting;
 	gp->waitreason = "stack split";
 	newstackcall = framesize==1;
@@ -727,13 +734,9 @@ runtime·newstack(void)\n \n \ttop->stackbase = gp->stackbase;\n \ttop->stackguard = gp->stackguard;\n-\ttop->gobuf = m->morebuf;\n-\ttop->argp = m->moreargp;\n+\ttop->gobuf = morebuf;\n+\ttop->argp = moreargp;\n \ttop->argsize = argsize;\n-\tm->moreargp = nil;\n-\tm->morebuf.pc = (uintptr)nil;\n-\tm->morebuf.lr = (uintptr)nil;\n-\tm->morebuf.sp = (uintptr)nil;\n \n \t// copy flag from panic\n \ttop->panic = gp->ispanic;\n```

## コアとなるコードの解説

このコミットの主要な変更点は、`runtime·newstack`関数内で`m->moreargp`と`m->morebuf`の値を一時変数に退避させ、その後すぐに`m`構造体内のこれらのフィールドをクリアしていることです。

1.  **新しい一時変数の導入**:
    ```c
    +	Gobuf label, morebuf;
    +	void *moreargp;
    ```
    `morebuf`と`moreargp`という新しいローカル変数が導入されました。これらは、`m`構造体から読み取った`m->morebuf`と`m->moreargp`の値を一時的に保持するために使用されます。

2.  **`m`フィールドのクリア**:
    ```c
    +	moreargp = m->moreargp;
    +	m->moreargp = nil;
    +	morebuf = m->morebuf;
    +	m->morebuf.pc = (uintptr)nil;
    +	m->morebuf.lr = (uintptr)nil;
    +	m->morebuf.sp = (uintptr)nil;
    ```
    この部分が変更の核心です。
    *   まず、`m->moreargp`の現在の値がローカル変数`moreargp`にコピーされます。
    *   **直後に**、`m->moreargp`が`nil`に設定されます。これにより、`m`構造体から古い引数ポインタへの参照が削除されます。
    *   同様に、`m->morebuf`の現在の値がローカル変数`morebuf`にコピーされます。
    *   **直後に**、`m->morebuf`の各フィールド(`pc`, `lr`, `sp`)が`nil`またはゼロ値に設定されます。これにより、`m`構造体から古い`Gobuf`コンテキストへの参照が削除されます。

3.  **`top`構造体への値のコピー**:
    ```c
    -\ttop->gobuf = m->morebuf;
    -\ttop->argp = m->moreargp;
    +\ttop->gobuf = morebuf;
    +\ttop->argp = moreargp;
    ```
    以前は`m->morebuf`と`m->moreargp`から直接`top`構造体(新しいスタックフレームの情報を保持する構造体)に値がコピーされていましたが、この変更により、一時変数`morebuf`と`moreargp`から値がコピーされるようになりました。これは、`m`構造体内のフィールドが既にクリアされているため、正しい値を`top`に渡すために必要です。

4.  **冗長なクリア処理の削除**:
    ```c
    -\tm->moreargp = nil;
    -\tm->morebuf.pc = (uintptr)nil;
    -\tm->morebuf.lr = (uintptr)nil;
    -\tm->morebuf.sp = (uintptr)nil;
    ```
    以前のコードでは、`top`構造体へのコピー後に再度`m->moreargp`と`m->morebuf`をクリアする処理がありましたが、これは新しいコードで既にクリアされているため、冗長となり削除されました。

この変更により、`m->moreargp`と`m->morebuf`が一時的な参照としてのみ機能し、スタック拡張やプリエンプションの処理が完了した後は、これらのフィールドが古いメモリ領域への参照を保持し続けることがなくなりました。これにより、ガベージコレクタが不要になったメモリを正確に識別し、解放できるようになり、メモリリークが解消されました。

## 関連リンク

*   Go CL: [https://golang.org/cl/80480044](https://golang.org/cl/80480044)
*   Issue 7633: [https://golang.org/issue/7633](https://golang.org/issue/7633)
*   Issue 7533: [https://golang.org/issue/7533](https://golang.org/issue/7533)

## 参考にした情報源リンク

*   Go言語のソースコード (`src/pkg/runtime/stack.c`)
*   Go言語のIssueトラッカー (`golang.org/issue/7633`, `golang.org/issue/7533`)
*   Go言語のコードレビューシステム (`golang.org/cl/80480044`)
*   Go言語の`sync.Pool`に関するドキュメントや解説記事 (一般的な知識として)
*   Goランタイムのスケジューラに関する一般的な解説記事 (M, P, Gモデル、スタック管理、プリエンプションなど)
*   ガベージコレクションの仕組みに関する一般的な知識```markdown
# [インデックス 18963] ファイルの概要

このコミットは、Goランタイムにおけるメモリリークの修正に関するものです。具体的には、`m->moreargp` および `m->morebuf` というフィールドが、ゴルーチンのプリエンプション(横取り)やスタック拡張の際に適切にクリアされなかったために発生していた、誤ったメモリ保持(false retention)を解消することを目的としています。これにより、大きなメモリブロックが永続的にリークする可能性があり、`sync.Pool` のファイナライザが失敗する問題を引き起こしていました。

## コミット

runtime: eliminate false retention due to m->moreargp/morebuf m->moreargp/morebuf were not cleared in case of preemption and stack growing, it can lead to persistent leaks of large memory blocks.

It seems to fix the sync.Pool finalizer failures. I've run the test 500'000 times w/o a single failure; previously it would fail dozens of times.

Fixes #7633. Fixes #7533.

LGTM=rsc R=golang-codereviews CC=golang-codereviews, khr, rsc https://golang.org/cl/80480044


## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/7c75a862b4df306a2573d1aa966d68803bc8b4e7](https://github.com/golang/go/commit/7c75a862b4df306a2573d1aa966d68803bc8b4e7)

## 元コミット内容

runtime: eliminate false retention due to m->moreargp/morebuf m->moreargp/morebuf were not cleared in case of preemption and stack growing, it can lead to persistent leaks of large memory blocks.

It seems to fix the sync.Pool finalizer failures. I've run the test 500'000 times w/o a single failure; previously it would fail dozens of times.

Fixes #7633. Fixes #7533.

LGTM=rsc R=golang-codereviews CC=golang-codereviews, khr, rsc https://golang.org/cl/80480044


## 変更の背景

このコミットの背景には、Goランタイムにおける特定のメモリリーク問題と、それに起因する`sync.Pool`のファイナライザの失敗がありました。

Goランタイムは、ゴルーチン(Goの軽量スレッド)のスタックを動的に管理します。ゴルーチンが関数呼び出しを深くネストしたり、大きなローカル変数を使用したりすると、スタックが不足する可能性があります。このような場合、ランタイムは自動的にスタックを拡張(stack growing)します。また、Goランタイムは協調的プリエンプション(cooperative preemption)を導入しており、長時間実行されるゴルーチンが他のゴルーチンにCPUを明け渡す機会を提供します。

問題は、これらのスタック拡張やプリエンプションのメカニズムと、`m`(machine)構造体内の`moreargp`および`morebuf`というフィールドの管理方法にありました。`m`構造体は、OSスレッド(M)とゴルーチン(G)のスケジューリングに関連する情報を保持しています。`moreargp`と`morebuf`は、スタック拡張やプリエンプションの際に、現在のゴルーチンの実行コンテキスト(特に引数ポインタやレジスタの状態)を一時的に保存するために使用されるフィールドでした。

しかし、これらのフィールドが、スタック拡張やプリエンプションが完了した後に適切にクリアされなかったため、古いスタックフレームや関連するメモリ領域への参照が残り続けてしまう「誤った保持(false retention)」が発生していました。ガベージコレクタは、これらの参照が存在する限り、参照されているメモリを解放できないため、結果としてメモリリークが発生していました。

このメモリリークは、特に`sync.Pool`のファイナライザの失敗という形で顕在化しました。`sync.Pool`は、オブジェクトの再利用を目的としたGoの標準ライブラリの機能です。`sync.Pool`に格納されたオブジェクトがガベージコレクタによって回収される際に、ファイナライザが実行されることがあります。メモリリークによってオブジェクトが適切に回収されないと、ファイナライザが期待通りに実行されず、テストの失敗やアプリケーションの不安定性につながっていました。コミットメッセージにあるように、この修正前は`sync.Pool`のテストが頻繁に失敗していたことが、問題の深刻さを示しています。

## 前提知識の解説

このコミットを理解するためには、以下のGoランタイムの概念を理解しておく必要があります。

1.  **ゴルーチン (Goroutine)**: Go言語の軽量な実行単位です。OSスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。Goランタイムがゴルーチンのスケジューリング、スタック管理、およびプリエンプションを担当します。

2.  **M (Machine) と G (Goroutine) と P (Processor)**: Goランタイムのスケジューラにおける主要な抽象化です。
    *   **M (Machine)**: OSスレッドを表します。Goのコードを実行する実際のOSスレッドです。
    *   **G (Goroutine)**: ゴルーチンを表します。
    *   **P (Processor)**: 論理プロセッサを表します。MがGを実行するためのコンテキストを提供します。PはMにアタッチされ、MはP上でGを実行します。

3.  **スタック管理 (Stack Management)**: Goのゴルーチンは、最初は小さなスタック(通常は数KB)で開始します。関数呼び出しが深くなったり、大きなローカル変数が割り当てられたりしてスタックが不足しそうになると、Goランタイムは自動的にスタックを拡張します(stack growing)。これは、より大きな新しいスタックを割り当て、古いスタックの内容を新しいスタックにコピーし、ゴルーチンのスタックポインタを更新することで行われます。

4.  **プリエンプション (Preemption)**: Goランタイムは、長時間実行されるゴルーチンが他のゴルーチンにCPUを明け渡すように、協調的プリエンプションを実装しています。これは、関数プロローグ(関数の開始部分)でスタックガードページをチェックすることで実現されます。スタックガードページに到達すると、ランタイムはゴルーチンの実行を一時停止し、スケジューラに制御を戻します。

5.  **`m` 構造体**: `runtime.m` 構造体は、各OSスレッド(M)の状態を保持します。これには、現在実行中のゴルーチン、スタック情報、スケジューリング関連のデータなどが含まれます。

6.  **`Gobuf` 構造体**: `runtime.Gobuf` 構造体は、ゴルーチンの実行コンテキスト(プログラムカウンタ `pc`、スタックポインタ `sp`、リンクレジスタ `lr` など)を保存するために使用されます。これは、ゴルーチンの切り替えや、スタック拡張、プリエンプションの際に、ゴルーチンの状態を保存・復元するために利用されます。

7.  **`m->moreargp` と `m->morebuf`**: これらは`m`構造体内のフィールドで、スタック拡張やプリエンプションの際に一時的にゴルーチンの状態を保存するために使用されます。
    *   `m->moreargp`: スタック拡張やプリエンプションの際に、呼び出し元の関数の引数へのポインタを一時的に保持するために使用されます。
    *   `m->morebuf`: スタック拡張やプリエンプションの際に、`Gobuf`構造体として、ゴルーチンの実行コンテキスト(`pc`, `sp`, `lr`など)を一時的に保持するために使用されます。

8.  **`sync.Pool`**: Goの標準ライブラリ`sync`パッケージに含まれる型で、一時的なオブジェクトの再利用を効率的に行うためのプールです。オブジェクトの生成コストが高い場合などに、オブジェクトをプールしておき、必要に応じて取り出し、使い終わったらプールに戻すことで、ガベージコレクションの負荷を軽減し、パフォーマンスを向上させます。`sync.Pool`に格納されたオブジェクトは、ガベージコレクションの対象となり、プールが不要になったオブジェクトを自動的に破棄するためにファイナライザが使用されることがあります。

## 技術的詳細

このコミットが修正する問題は、Goランタイムの`runtime·newstack`関数にありました。この関数は、ゴルーチンのスタックが不足した際に新しいスタックを割り当て、古いスタックの内容をコピーし、ゴルーチンの実行を新しいスタックに切り替える役割を担っています。また、プリエンプションが発生した場合も、この関数が呼び出され、ゴルーチンのコンテキストが保存されます。

問題の核心は、`m->moreargp`と`m->morebuf`という`m`構造体のフィールドが、スタック拡張やプリエンプションの処理中に一時的にゴルーチンの状態を保持するために使用されるにもかかわらず、その処理が完了した後にこれらのフィールドが適切にクリア(`nil`やゼロ値に設定)されていなかった点です。

具体的には、`runtime·newstack`関数内で、現在のゴルーチンの引数ポインタと`Gobuf`コンテキストが`m->moreargp`と`m->morebuf`に保存されます。その後、新しいスタックが設定され、ゴルーチンの実行が再開されます。しかし、この再開後も`m->moreargp`と`m->morebuf`には古いスタックフレームやコンテキストへの参照が残ったままでした。

ガベージコレクタは、到達可能なすべてのオブジェクトをマークし、マークされていないオブジェクトを解放します。`m->moreargp`と`m->morebuf`が古いスタックフレームへの参照を保持していると、たとえそのスタックフレームが論理的には不要になっていたとしても、ガベージコレクタはそれを到達可能と判断し、解放しませんでした。これが「誤った保持(false retention)」であり、結果としてメモリリークを引き起こしていました。

このメモリリークは、特に`sync.Pool`のファイナライザの失敗という形で表面化しました。`sync.Pool`は、オブジェクトを再利用するために、内部的にガベージコレクタと連携します。プール内のオブジェクトが不要になり、ガベージコレクタによって回収される際に、そのオブジェクトに設定されたファイナライザが実行されることがあります。しかし、`m->moreargp`や`m->morebuf`による誤った参照が原因で、`sync.Pool`が管理するオブジェクトが期待通りに回収されず、ファイナライザが実行されない、あるいは遅延して実行されるといった問題が発生していました。これにより、`sync.Pool`のテストが不安定になり、頻繁に失敗する原因となっていました。

このコミットでは、`runtime·newstack`関数内で、`m->moreargp`と`m->morebuf`の値を一時変数に退避させた後、すぐに`m`構造体内のこれらのフィールドを`nil`またはゼロ値にクリアするように変更しています。これにより、古いスタックフレームへの参照が永続的に保持されることがなくなり、ガベージコレクタが不要になったメモリを適切に解放できるようになりました。結果として、メモリリークが解消され、`sync.Pool`のファイナライザが期待通りに動作するようになり、関連するテストの失敗が解消されました。

## コアとなるコードの変更箇所

変更は `src/pkg/runtime/stack.c` ファイルの `runtime·newstack` 関数内で行われています。

```diff
--- a/src/pkg/runtime/stack.c
+++ b/src/pkg/runtime/stack.c
@@ -608,7 +608,8 @@ runtime·newstack(void)
 	uintptr sp;
 	uintptr *src, *dst, *dstend;
 	G *gp;
-	Gobuf label;
+	Gobuf label, morebuf;
+	void *moreargp;
 	bool newstackcall;
 
 	if(m->forkstackguard)
@@ -627,6 +628,12 @@ runtime·newstack(void)
 
 	framesize = m->moreframesize;
 	argsize = m->moreargsize;
+	moreargp = m->moreargp;
+	m->moreargp = nil;
+	morebuf = m->morebuf;
+	m->morebuf.pc = (uintptr)nil;
+	m->morebuf.lr = (uintptr)nil;
+	m->morebuf.sp = (uintptr)nil;
 	gp->status = Gwaiting;
 	gp->waitreason = "stack split";
 	newstackcall = framesize==1;
@@ -727,13 +734,9 @@ runtime·newstack(void)\n \n \ttop->stackbase = gp->stackbase;\n \ttop->stackguard = gp->stackguard;\n-\ttop->gobuf = m->morebuf;\n-\ttop->argp = m->moreargp;\n+\ttop->gobuf = morebuf;\n+\ttop->argp = moreargp;\n \ttop->argsize = argsize;\n-\tm->moreargp = nil;\n-\tm->morebuf.pc = (uintptr)nil;\n-\tm->morebuf.lr = (uintptr)nil;\n-\tm->morebuf.sp = (uintptr)nil;\n \n \t// copy flag from panic\n \ttop->panic = gp->ispanic;\n```

## コアとなるコードの解説

このコミットの主要な変更点は、`runtime·newstack`関数内で`m->moreargp`と`m->morebuf`の値を一時変数に退避させ、その後すぐに`m`構造体内のこれらのフィールドをクリアしていることです。

1.  **新しい一時変数の導入**:
    ```c
    +	Gobuf label, morebuf;
    +	void *moreargp;
    ```
    `morebuf`と`moreargp`という新しいローカル変数が導入されました。これらは、`m`構造体から読み取った`m->morebuf`と`m->moreargp`の値を一時的に保持するために使用されます。

2.  **`m`フィールドのクリア**:
    ```c
    +	moreargp = m->moreargp;
    +	m->moreargp = nil;
    +	morebuf = m->morebuf;
    +	m->morebuf.pc = (uintptr)nil;
    +	m->morebuf.lr = (uintptr)nil;
    +	m->morebuf.sp = (uintptr)nil;
    ```
    この部分が変更の核心です。
    *   まず、`m->moreargp`の現在の値がローカル変数`moreargp`にコピーされます。
    *   **直後に**、`m->moreargp`が`nil`に設定されます。これにより、`m`構造体から古い引数ポインタへの参照が削除されます。
    *   同様に、`m->morebuf`の現在の値がローカル変数`morebuf`にコピーされます。
    *   **直後に**、`m->morebuf`の各フィールド(`pc`, `lr`, `sp`)が`nil`またはゼロ値に設定されます。これにより、`m`構造体から古い`Gobuf`コンテキストへの参照が削除されます。

3.  **`top`構造体への値のコピー**:
    ```c
    -\ttop->gobuf = m->morebuf;
    -\ttop->argp = m->moreargp;
    +\ttop->gobuf = morebuf;
    +\ttop->argp = moreargp;
    ```
    以前は`m->morebuf`と`m->moreargp`から直接`top`構造体(新しいスタックフレームの情報を保持する構造体)に値がコピーされていましたが、この変更により、一時変数`morebuf`と`moreargp`から値がコピーされるようになりました。これは、`m`構造体内のフィールドが既にクリアされているため、正しい値を`top`に渡すために必要です。

4.  **冗長なクリア処理の削除**:
    ```c
    -\tm->moreargp = nil;
    -\tm->morebuf.pc = (uintptr)nil;
    -\tm->morebuf.lr = (uintptr)nil;
    -\tm->morebuf.sp = (uintptr)nil;
    ```
    以前のコードでは、`top`構造体へのコピー後に再度`m->moreargp`と`m->morebuf`をクリアする処理がありましたが、これは新しいコードで既にクリアされているため、冗長となり削除されました。

この変更により、`m->moreargp`と`m->morebuf`が一時的な参照としてのみ機能し、スタック拡張やプリエンプションの処理が完了した後は、これらのフィールドが古いメモリ領域への参照を保持し続けることがなくなりました。これにより、ガベージコレクタが不要になったメモリを正確に識別し、解放できるようになり、メモリリークが解消されました。

## 関連リンク

*   Go CL: [https://golang.org/cl/80480044](https://golang.org/cl/80480044)
*   Issue 7633: [https://golang.org/issue/7633](https://golang.org/issue/7633)
*   Issue 7533: [https://golang.org/issue/7533](https://golang.org/issue/7533)

## 参考にした情報源リンク

*   Go言語のソースコード (`src/pkg/runtime/stack.c`)
*   Go言語のIssueトラッカー (`golang.org/issue/7633`, `golang.org/issue/7533`)
*   Go言語のコードレビューシステム (`golang.org/cl/80480044`)
*   Go言語の`sync.Pool`に関するドキュメントや解説記事 (一般的な知識として)
*   Goランタイムのスケジューラに関する一般的な解説記事 (M, P, Gモデル、スタック管理、プリエンプションなど)
*   ガベージコレクションの仕組みに関する一般的な知識