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

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

このコミットは、Goランタイムのsrc/pkg/runtime/proc.cファイルに対する変更であり、以前のコミット(CL 12167043 / 475e11851fc1)によって導入された変更を元に戻す(undo)ことを目的としています。具体的には、sysmonスレッドのパーキング(待機)ロジックと、SIGPROFシグナル処理におけるスタックトレース生成ロジックの一部が、元の状態に戻されています。

コミット

commit 6ee69a97269eb26186d08832dcafd9432945f5ad
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Wed Jul 31 20:03:05 2013 +0400

    undo CL 12167043 / 475e11851fc1
    
    Submitted with some unrelated changes that were not intended to go in.
    
    ««« original CL description
    runtime: do not park sysmon thread if any goroutines are running
    Sysmon thread parks if no goroutines are running (runtime.sched.npidle == runtime.gomaxprocs).
    Currently it's unparked when a goroutine enters syscall, it was enough
    to retake P's from blocking syscalls.
    But it's not enough for reliable goroutine preemption. We need to ensure that
    sysmon runs if any goroutines are running.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/12167043
    »»»
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/12171044

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

https://github.com/golang/go/commit/6ee69a97269eb26186d08832dcafd9432945f5ad

元コミット内容

このコミットが元に戻した、以前のコミット(CL 12167043)の意図は以下の通りです。

runtime: do not park sysmon thread if any goroutines are running
Sysmon thread parks if no goroutines are running (runtime.sched.npidle == runtime.gomaxprocs).
Currently it's unparked when a goroutine enters syscall, it was enough
to retake P's from blocking syscalls.
But it's not enough for reliable goroutine preemption. We need to ensure that
sysmon runs if any goroutines are running.

要約すると、「sysmonスレッドは、Goroutineが実行されていない場合に待機状態に入るが、Goroutineが実行されている場合は待機状態に入らないようにする。これは、システムコールに入ったGoroutineからPを再取得するだけでは不十分であり、Goroutineのプリエンプションの信頼性を確保するためには、Goroutineが実行されている限りsysmonが動作し続ける必要がある」というものでした。

変更の背景

このコミットの背景は、コミットメッセージに明確に記載されています。「Submitted with some unrelated changes that were not intended to go in.」つまり、以前のコミット(CL 12167043 / 475e11851fc1)には、本来含まれるべきではない「無関係な変更」が含まれていました。

ソフトウェア開発において、特定の機能改善やバグ修正を目的としたコミットに、その目的とは直接関係のないコード変更が混入してしまうことは稀ではありません。このような場合、コードベースの整合性を保ち、意図しない副作用や将来的な問題を避けるために、一度その変更全体を元に戻し、必要な変更のみを改めてコミットし直すというプラクティスが取られます。

このコミットは、まさにその「クリーンアップ」のプロセスの一環として行われました。以前のコミットがsysmonスレッドの挙動改善とGoroutineのプリエンプション信頼性向上を目指していたものの、その実装に不適切な部分があったため、一度その変更全体を破棄し、コードベースを安定した状態に戻すことが決定されたのです。

前提知識の解説

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

  • Goランタイム (Go Runtime): Goプログラムの実行を管理する中核部分です。これには、Goroutineスケジューラ、ガベージコレクタ (GC)、メモリ管理、システムコールインターフェースなどが含まれます。Goプログラムは、OSによって直接実行されるのではなく、このランタイム上で動作します。

  • Goroutine (ゴルーチン): Go言語における軽量な並行実行単位です。OSスレッドよりもはるかに軽量で、数百万のGoroutineを同時に実行することも可能です。GoroutineのスケジューリングはGoランタイムによって行われます。

  • P (Processor): Goスケジューラにおける「論理プロセッサ」または「コンテキスト」です。Goroutineを実行するためのリソース(実行キュー、ローカルなメモリキャッシュなど)を提供します。GOMAXPROCS環境変数によって、同時に実行可能なPの数が制御されます。各Pは1つのOSスレッド(M)にアタッチされ、その上でGoroutineを実行します。

  • M (Machine): OSスレッドを表します。MはPを保持し、そのP上でGoroutineを実行します。Mはシステムコールを実行したり、ブロッキング操作を行ったりする際にOSと直接やり取りします。

  • Sysmon (System Monitor) スレッド: Goランタイムの非常に重要なバックグラウンドスレッドです。その主な役割は以下の通りです。

    • プリエンプション (Preemption): 長時間CPUを占有しているGoroutineを強制的に中断し、他のGoroutineにCPUを明け渡させることで、公平なスケジューリングを保証します。
    • ネットワークポーラーの実行: ネットワークI/Oなどの非同期イベントを監視し、準備ができたGoroutineをウェイクアップします。
    • ガベージコレクション (GC) のトリガー: GCの実行を定期的にチェックし、必要に応じてGCをトリガーします。
    • アイドル状態のPの解放: 長時間アイドル状態のPをOSに解放し、リソースを節約します。
    • デッドロックの検出: デッドロック状態にあるGoroutineを検出します。 sysmonスレッドは、これらのタスクを定期的に実行するために、通常はスリープとウェイクアップを繰り返します。
  • プリエンプション (Preemption): GoroutineがCPUを長時間占有するのを防ぎ、他のGoroutineにも実行機会を与えるためのメカニズムです。Go 1.2以前のバージョンでは、プリエンプションは主に協調的(cooperative)であり、Goroutineが関数呼び出しやシステムコールなどの特定のポイントに到達したときにのみ発生しました。しかし、無限ループなどでこれらのポイントに到達しないGoroutineは、他のGoroutineの実行を妨げる可能性がありました。この時期(2013年)は、より信頼性の高い非同期プリエンプションの導入が模索されていた時期であり、sysmonスレッドの役割が注目されていました。

  • runtime.sched.sysmonwait: Goランタイムのスケジューラ構造体runtime.sched内のフラグで、sysmonスレッドが現在待機状態にあるかどうかを示します。

  • runtime.notewakeup: Goランタイム内部で使用される同期プリミティブの一つで、特定のイベントを待っているスレッド(この場合はsysmonスレッド)をウェイクアップするために使用されます。

  • runtime.sigprof: プロファイリングのためにOSからGoプロセスに送信されるSIGPROFシグナルを処理するランタイム関数です。このシグナルは、CPUプロファイリングを行う際に、一定間隔でプログラムの実行状態(スタックトレースなど)をサンプリングするために使用されます。

  • runtime.gentraceback: 現在実行中のGoroutineのスタックトレース(関数呼び出しの履歴)を生成するランタイム関数です。デバッグやプロファイリングにおいて重要な情報を提供します。

技術的詳細

このコミットは、以前のコミット(CL 12167043)によってsrc/pkg/runtime/proc.cに導入された変更を完全に元に戻すものです。元に戻された変更は、主に以下の2つの領域に影響を与えていました。

  1. sysmonスレッドのウェイクアップロジックの変更:

    • 以前のコミットでは、exitsyscallfastexitsyscall0という2つの関数に、sysmonスレッドを積極的にウェイクアップするロジックが追加されていました。
    • exitsyscallfastは、Goroutineがシステムコールから高速パスで戻る際に呼び出されます。
    • exitsyscall0は、Goroutineがシステムコールから通常パスで戻る際に呼び出されます。
    • これらの関数内で、もしPがアイドル状態になったり、Goroutineがグローバル実行キューに戻されたりした際に、runtime.sched.sysmonwaitフラグがセットされていれば(つまりsysmonスレッドが待機状態であれば)、runtime.notewakeup(&runtime.sched.sysmonnote)を呼び出してsysmonスレッドを強制的にウェイクアップするようになっていました。
    • この変更の意図は、sysmonスレッドがより頻繁に実行されるようにすることで、特にGoroutineのプリエンプションの機会を増やし、プリエンプションの信頼性を向上させることにありました。システムコールから戻ったGoroutineがCPUを再取得する際に、sysmonがすぐに動作し、必要に応じてプリエンプションを実行できるようにするためです。
    • この「undo」コミットでは、これらの追加されたsysmonウェイクアップロジックが削除され、元の挙動に戻されています。
  2. runtime.sigprof関数におけるスタックトレース生成ロジックの変更:

    • 以前のコミットでは、runtime.sigprof関数(プロファイリングシグナルを処理する関数)内で、スタックトレースを生成するかどうかを制御する複雑な条件分岐が導入されていました。
    • 具体的には、tracebackというブール変数が導入され、特定の条件下(例えば、Windows環境でない場合、mm->mcachenilの場合、gpm->g0m->gsignalの場合、m->racecalltrueの場合など)でtracebackfalseに設定され、スタックトレースの生成がスキップされる可能性がありました。
    • また、Systemという空の関数が追加されていましたが、これはおそらくデバッグや特定のコンテキストでのみ使用されることを意図したもので、最終的には削除されています。
    • この「undo」コミットでは、これらのtracebackフラグに関連する条件分岐と、System関数の定義が削除され、runtime.sigprof関数はよりシンプルなスタックトレース生成ロジック(常にruntime.gentracebackを呼び出す)に戻されています。これは、元の変更が意図しない副作用やバグを引き起こす可能性があったため、または単にその変更がこのコミットの目的とは無関係であったためと考えられます。

このコミットは、特定の機能改善(sysmonの挙動とプリエンプション)を試みたものの、その実装が不完全であったり、意図しない変更を含んでいたために、一度その変更を破棄し、コードベースをクリーンな状態に戻すという、開発プロセスにおける健全なプラクティスを示しています。

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

変更はsrc/pkg/runtime/proc.cファイルのみで行われています。

--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -1536,10 +1536,6 @@ exitsyscallfast(void)
  if(runtime·sched.pidle) {
  	runtime·lock(&runtime·sched);
  	p = pidleget();
- 	if(p && runtime·atomicload(&runtime·sched.sysmonwait)) {
- 		runtime·atomicstore(&runtime·sched.sysmonwait, 0);
- 		runtime·notewakeup(&runtime·sched.sysmonnote);
- 	}
  	runtime·unlock(&runtime·sched);
  	if(p) {
  		acquirep(p);
@@ -1563,10 +1559,6 @@ exitsyscall0(G *gp)
  p = pidleget();
  if(p == nil)
  	globrunqput(gp);
- else if(runtime·atomicload(&runtime·sched.sysmonwait)) {
- 	runtime·atomicstore(&runtime·sched.sysmonwait, 0);
- 	runtime·notewakeup(&runtime·sched.sysmonnote);
- }
  runtime·unlock(&runtime·sched);
  if(p) {
  	acquirep(p);
@@ -1932,38 +1924,24 @@ static struct {
  	uintptr pcbuf[100];
  } prof;
  
-static void
-System(void)
-{
-}
-
 // Called if we receive a SIGPROF signal.
 void
 runtime·sigprof(uint8 *pc, uint8 *sp, uint8 *lr, G *gp)
 {
  int32 n;
- bool traceback;
  
- if(prof.fn == nil || prof.hz == 0)
- 	return;
- traceback = true;
  // Windows does profiling in a dedicated thread w/o m.
  if(!Windows && (m == nil || m->mcache == nil))
- 	traceback = false;
- if(gp == m->g0 || gp == m->gsignal)
- 	traceback = false;
- if(m != nil && m->racecall)
- 	traceback = false;
+\t\treturn;\n+\tif(prof.fn == nil || prof.hz == 0)\n+\t\treturn;\n  
  runtime·lock(&prof);
  if(prof.fn == nil) {
  	runtime·unlock(&prof);
  	return;
  }
- n = 1;
- prof.pcbuf[0] = (uintptr)pc;
- if(traceback)
- 	n = runtime·gentraceback((uintptr)pc, (uintptr)sp, (uintptr)lr, gp, 0, prof.pcbuf, nelem(prof.pcbuf), nil, nil, false);
+\tn = runtime·gentraceback((uintptr)pc, (uintptr)sp, (uintptr)lr, gp, 0, prof.pcbuf, nelem(prof.pcbuf), nil, nil, false);\n  if(n > 0)
  	prof.fn(prof.pcbuf, n);
  runtime·unlock(&prof);

コアとなるコードの解説

このコミットは、主に以下の3つのセクションでコードを削除しています。

  1. exitsyscallfast 関数内の削除:

    • 削除されたコードは、Goroutineがシステムコールから高速に復帰する際に、アイドル状態のPが存在し、かつsysmonスレッドが待機中(runtime.sched.sysmonwaitがセットされている)であれば、sysmonスレッドをウェイクアップするロジックでした。
    • 具体的には、runtime·atomicstore(&runtime·sched.sysmonwait, 0);sysmonwaitフラグをクリアし、runtime·notewakeup(&runtime·sched.sysmonnote);sysmonスレッドを通知して起こしていました。
    • この削除により、システムコールからの復帰時にsysmonスレッドを強制的にウェイクアップする挙動が元に戻されました。
  2. exitsyscall0 関数内の削除:

    • exitsyscall0は、Goroutineがシステムコールから通常パスで復帰する際に呼び出される関数です。
    • ここでも同様に、sysmonスレッドが待機中であればウェイクアップするロジックが削除されました。
    • この変更も、sysmonスレッドのウェイクアップに関する以前の挙動を復元するものです。
  3. runtime·sigprof 関数内の削除と変更:

    • Systemという空の静的関数が削除されました。これは以前のコミットで追加されたもので、このコミットの目的とは無関係であったため削除されたと考えられます。
    • runtime·sigprof関数内では、tracebackというブール変数の宣言と、それを用いた複数の条件分岐が削除されました。
    • 以前のコードでは、tracebackフラグに基づいてruntime·gentracebackを呼び出すかどうかが制御されていました。例えば、Windows環境でない場合や、特定のMやGの状態(m->g0, m->gsignal, m->racecallなど)に応じて、スタックトレースの生成をスキップする可能性がありました。
    • このコミットにより、これらの複雑な条件分岐が取り除かれ、runtime·sigprofは常にruntime·gentracebackを呼び出してスタックトレースを生成する、よりシンプルなロジックに戻されました。これは、プロファイリングの際に常に完全なスタックトレースを取得できるようにするため、または以前の変更が意図しない挙動を引き起こす可能性があったためと考えられます。

これらの変更は、以前のコミットが導入したsysmonスレッドの積極的なウェイクアップと、SIGPROF処理におけるスタックトレース生成の複雑な条件分岐を元に戻すことで、Goランタイムのコードベースを、意図しない変更が混入する前の状態に復元することを目的としています。

関連リンク

参考にした情報源リンク