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

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

このコミットは、Go言語のCgoテストスイートに、再帰的な内部OSスレッドロックに関するテストケースを追加するものです。具体的には、misc/cgo/test/issue3775.go ファイルに新しい init 関数が追加され、C.lockOSThreadC() が呼び出されています。これは、以前の変更(CL 11663043)によって引き起こされる可能性のあった問題を検出するために設計されています。

コミット

commit 4d17efe81b88b0ffacb74986d2558e9098afdded
Author: Russ Cox <rsc@golang.org>
Date:   Tue Jul 23 14:43:55 2013 -0400

    misc/cgo/test: test recursive internal OS thread lock
    
    This would have failed with CL 11663043.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/11480045

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

https://github.com/golang/go/commit/4d17efe81b88b0ffacb74986d2558e9098afdded

元コミット内容

このコミットは、GoのCgoテストスイートに新しいテストケースを追加します。その目的は、GoランタイムがOSスレッドをロックする際の再帰的な呼び出しが正しく処理されることを確認することです。特に、以前の変更(CL 11663043)が導入された場合に発生する可能性があった問題を特定するために作成されました。このテストは、init 関数内でOSスレッドロックを呼び出すことで、Goランタイムの初期化フェーズとCgo呼び出しの両方でスレッドロックが二重に発生するシナリオをシミュレートします。

変更の背景

Goランタイムは、特定の操作(特にCgo呼び出し)において、現在のGoルーチンが特定のOSスレッドに固定されるように、OSスレッドをロックするメカニズムを持っています。これは、Cコードがスレッドローカルストレージや特定のOSスレッドに依存するAPIを使用する場合に重要となります。

このコミットの背景には、Goランタイム内部でのOSスレッドロックの管理方法に関する変更(CL 11663043)があったと考えられます。この変更は、スレッドロックの取得と解放のロジックに影響を与え、特定の状況下で問題を引き起こす可能性がありました。具体的には、init 関数のようなGoランタイムの初期化フェーズで既にOSスレッドがロックされている状態で、さらにCgo呼び出しによってOSスレッドロックが要求されるような「再帰的な」シナリオで、デッドロックやパニックが発生する可能性が懸念されました。

このコミットは、そのような潜在的な問題を事前に検出し、Goランタイムの堅牢性を高めるために、意図的に問題を引き起こす可能性のあるシナリオをテストケースとして追加したものです。

前提知識の解説

GoルーチンとOSスレッド

Go言語では、Goルーチンは軽量な実行単位であり、OSスレッドとは異なります。Goランタイムは、Goルーチンを少数のOSスレッドに多重化して実行します。このGoルーチンとOSスレッドのマッピングは、Goスケジューラによって動的に管理されます。

CgoとOSスレッドのロック

Cgoは、GoプログラムからC言語のコードを呼び出すためのメカニズムです。C言語のライブラリには、特定のOSスレッドに依存するものが存在します(例: GUIライブラリ、一部のネットワークライブラリ)。このようなCコードをGoから呼び出す際、Goルーチンが実行されているOSスレッドが途中で変更されると、Cコードが予期せぬ動作をする可能性があります。

この問題を回避するため、Goランタイムは runtime.LockOSThread() 関数を提供しています。この関数を呼び出すと、現在のGoルーチンが実行されているOSスレッドに固定され、そのGoルーチンが終了するか runtime.UnlockOSThread() が呼び出されるまで、他のGoルーチンはそのOSスレッドで実行されなくなります。Cgo呼び出しの内部でも、必要に応じてこのOSスレッドロックのメカニズムが利用されることがあります。

init 関数

Go言語の init 関数は、パッケージが初期化される際に自動的に実行される特殊な関数です。main 関数が呼び出される前に実行され、パッケージレベルの初期化処理(変数の初期化、設定の読み込みなど)に使用されます。init 関数は、Goランタイムによって呼び出されるため、その実行コンテキストは通常のGoルーチンとは異なる場合があります。

CL 11663043 (参照された変更リスト)

コミットメッセージで参照されている CL 11663043 は、Goの内部的な変更リスト(Change List)の番号です。Goプロジェクトでは、Gerritというコードレビューシステムを使用して変更を管理しており、各変更には一意のCL番号が割り当てられます。このCL 11663043は、GoランタイムのOSスレッドロックに関する内部的な変更であったと推測されます。残念ながら、公開されているGoの変更履歴からはこの特定のCL番号の詳細を特定することはできませんでした。これは、内部的な開発ブランチや、後に統合された別のCLの一部であった可能性、あるいは単なる参照ミスである可能性も考えられます。しかし、このコミットの意図から、このCLがOSスレッドロックのメカニズムに何らかの変更を加え、その変更が再帰的なロックシナリオで問題を引き起こす可能性があったと理解できます。

技術的詳細

このコミットは、misc/cgo/test/issue3775.go ファイルに新しい init 関数を追加することで、GoランタイムのOSスレッドロックの堅牢性をテストしています。

既存の test3775 関数は、C.lockOSThreadC() を呼び出してOSスレッドをロックし、その後 UnlockOSThread が原因でパニックが発生しないことを確認するテストです。このコミットで追加された init 関数は、test3775 と同様に C.lockOSThreadC() を呼び出しますが、その呼び出しが init フェーズで行われる点が重要です。

init 関数はGoランタイムによって呼び出されるため、この時点で既にGoランタイムが内部的にOSスレッドをロックしている可能性があります。その状態で、さらに C.lockOSThreadC() が呼び出されると、OSスレッドロックが二重に、あるいは再帰的に要求されることになります。

このシナリオは、GoランタイムがOSスレッドロックを管理する方法において、特定のコーナーケースを浮き彫りにします。もしランタイムが再帰的なロック要求を適切に処理できない場合、デッドロック、パニック、または不正な状態に陥る可能性があります。このテストは、CL 11663043のような変更が、このような再帰的なロックシナリオを壊さないことを保証するために追加されました。

C.lockOSThreadC() は、Cgoを介してC言語の関数 lockOSThreadC を呼び出していることを示唆しています。このC関数は、Goランタイムの runtime.LockOSThread() に対応するCgo側のラッパーである可能性が高いです。これにより、GoルーチンがOSスレッドに固定される動作をCgoのコンテキストでテストできます。

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

diff --git a/misc/cgo/test/issue3775.go b/misc/cgo/test/issue3775.go
index c05a5d4be8..8f81854195 100644
--- a/misc/cgo/test/issue3775.go
+++ b/misc/cgo/test/issue3775.go
@@ -15,6 +15,14 @@ import (
 	"testing"
 )
 
+func init() {
+	// Same as test3775 but run during init so that
+	// there are two levels of internal runtime lock
+	// (1 for init, 1 for cgo).
+	// This would have been broken by CL 11663043.
+	C.lockOSThreadC()
+}
+
 func test3775(t *testing.T) {
 	// Used to panic because of the UnlockOSThread below.
 	C.lockOSThreadC()

コアとなるコードの解説

追加されたコードは、issue3775.go ファイル内の init 関数です。

func init() {
	// Same as test3775 but run during init so that
	// there are two levels of internal runtime lock
	// (1 for init, 1 for cgo).
	// This would have been broken by CL 11663043.
	C.lockOSThreadC()
}
  • func init() { ... }: これはGoの init 関数であり、このパッケージがインポートされる際に自動的に実行されます。
  • // Same as test3775 but run during init so that ...: コメントは、この init 関数が既存の test3775 関数と同様の目的を持つが、実行タイミングが init フェーズである点を強調しています。
  • // there are two levels of internal runtime lock (1 for init, 1 for cgo).: ここがこのテストの核心です。init 関数が実行される時点で、Goランタイムは既に内部的にOSスレッドをロックしている可能性があります。その上で、C.lockOSThreadC() が呼び出されることで、OSスレッドロックが「二重に」または「再帰的に」要求される状況を作り出しています。
  • // This would have been broken by CL 11663043.: このコメントは、以前のCL 11663043が、このような再帰的なロックシナリオを正しく処理できなかった可能性を示唆しています。このテストが追加されたことで、将来的に同様の問題が再発しないように、GoランタイムのOSスレッドロックメカニズムが堅牢であることを保証します。
  • C.lockOSThreadC(): これはCgoの構文で、C言語で定義された lockOSThreadC 関数を呼び出しています。このC関数は、Goランタイムの runtime.LockOSThread() に対応する処理を実行し、現在のGoルーチンが実行されているOSスレッドをロックします。

この init 関数がパニックを起こさずに正常に完了すれば、Goランタイムが再帰的なOSスレッドロックを適切に処理できることが証明されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特に runtime パッケージと cmd/cgo
  • Go言語のコミット履歴 (Gerrit)
  • Go言語の init 関数に関する情報
  • OSスレッドとGoルーチンの関係に関する一般的な情報
  • GoのCL番号に関する一般的な知識