[インデックス 18202] ファイルの概要
このコミットは、GoランタイムがLinuxのNPTL (Native POSIX Thread Library) のpthread_cancel
機能と共存できるようにするための重要な変更を導入しています。具体的には、GoランタイムがNPTLがスレッドキャンセルに使用するSIGRTMIN
シグナル(シグナル32)のハンドラを上書きしないように修正することで、Cライブラリがpthread_cancel
を呼び出した際にGoプログラムが異常終了する問題を解決しています。
コミット
commit c4770b991b7ad56660d45a02213b71bbb6361e8e
Author: Rowan Worth <sqweek@gmail.com>
Date: Thu Jan 9 09:34:04 2014 -0800
runtime: co-exist with NPTL's pthread_cancel.
NPTL uses SIGRTMIN (signal 32) to effect thread cancellation.
Go's runtime replaces NPTL's signal handler with its own, and
ends up aborting if a C library that ends up calling
pthread_cancel is used.
This patch prevents runtime from replacing NPTL's handler.
Fixes #6997.
R=golang-codereviews, iant, dvyukov
CC=golang-codereviews
https://golang.org/cl/47540043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c4770b991b7ad56660d45a02213b71bbb6361e8e
元コミット内容
GoランタイムがNPTLのpthread_cancel
と共存できるようにする。
NPTLはスレッドキャンセルを実行するためにSIGRTMIN
(シグナル32)を使用する。GoのランタイムはNPTLのシグナルハンドラを独自のものに置き換えており、その結果、pthread_cancel
を呼び出すCライブラリが使用されると異常終了してしまう。
このパッチは、ランタイムがNPTLのハンドラを置き換えるのを防ぐ。
Issue #6997 を修正。
変更の背景
この変更の背景には、GoプログラムがCgo(GoとC言語の相互運用機能)を介してCライブラリを使用する際に発生する特定のシグナル処理の競合問題があります。
Linuxシステムにおいて、POSIXスレッド(pthreads)の実装であるNPTLは、スレッドのキャンセル(pthread_cancel
)を内部的に処理するためにリアルタイムシグナルであるSIGRTMIN
(通常はシグナル32)を利用します。スレッドがキャンセルされると、NPTLはこのシグナルを対象スレッドに送信し、そのシグナルハンドラがスレッドのクリーンアップと終了を処理します。
一方、Goランタイムもまた、ガベージコレクション、スケジューリング、プロファイリングなどの内部的な目的のために、独自のシグナルハンドラを設定します。問題は、Goランタイムが起動時にシステムデフォルトのシグナルハンドラを上書きする際に、NPTLがpthread_cancel
のために設定したSIGRTMIN
のハンドラも誤って上書きしてしまっていた点にありました。
この上書きが発生すると、GoプログラムがCgoを介してpthread_cancel
を呼び出すCライブラリ(例えば、一部のネットワークライブラリやシステムライブラリ)を使用した場合、NPTLがSIGRTMIN
を送信しても、Goランタイムのハンドラがそれを適切に処理できず、結果としてプログラムが異常終了(abort)してしまうというバグ(Issue #6997)が発生していました。
このコミットは、GoランタイムがSIGRTMIN
に対するNPTLのハンドラを尊重し、上書きしないようにすることで、この競合を解消し、GoプログラムとCライブラリの間のより堅牢な相互運用性を確保することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が必要です。
-
NPTL (Native POSIX Thread Library): LinuxにおけるPOSIXスレッド(pthreads)の標準的な実装です。NPTLは、ユーザー空間でスレッドを管理し、カーネルの軽量プロセス(LWP)にマッピングすることで、効率的なマルチスレッドプログラミングを可能にします。
pthread_cancel
のようなスレッド操作は、NPTLによって提供されます。 -
pthread_cancel
: POSIXスレッドAPIの一部で、指定されたスレッドの実行を非同期的に終了させるための関数です。キャンセルされたスレッドは、キャンセルポイント(例:sleep
,read
,write
などのシステムコール)に到達した際に終了するか、非同期キャンセルが有効な場合はいつでも終了します。NPTLは、このキャンセル処理を内部的にシグナルを用いて実現しています。 -
リアルタイムシグナル (Real-time Signals): Linuxカーネルが提供するシグナルの一種で、従来の標準シグナル(
SIGINT
,SIGTERM
など)とは異なり、キューイングが可能で、シグナル番号の範囲が異なります。SIGRTMIN
からSIGRTMAX
までの範囲がリアルタイムシグナルとして利用可能です。NPTLは、これらのシグナルを内部的なスレッド管理(特にpthread_cancel
)に利用することがあります。SIGRTMIN
は、システムによってその値が異なることがありますが、多くのLinuxシステムでは32番のシグナルとして定義されています。 -
シグナルハンドラ: 特定のシグナルがプロセスに送信されたときに実行される関数です。プロセスは
sigaction
システムコールなどを使用して、特定のシグナルに対するカスタムハンドラを設定できます。Goランタイムも、自身の内部処理のためにシグナルハンドラを設定します。 -
Cgo: Go言語の機能の一つで、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりすることを可能にします。これにより、既存のCライブラリをGoプロジェクトで再利用したり、パフォーマンスが重要な部分をCで記述したりすることができます。Cgoを使用する際、GoランタイムとCライブラリの間のシグナル処理の相互作用が問題となることがあります。
-
Goランタイムのシグナル処理: Goランタイムは、ガベージコレクションのトリガー、プロファイリング、デッドロック検出、およびプログラムの異常終了(パニック)処理など、様々な内部目的のためにシグナルを使用します。Goは独自のスケジューラとスレッドモデルを持っているため、OSレベルのシグナルを適切に処理し、Goのゴルーチンモデルと統合する必要があります。このため、Goランタイムは起動時に多くの標準シグナルに対するハンドラを独自のものに置き換えます。
技術的詳細
このコミットの技術的な核心は、Goランタイムがシグナルハンドラを設定する際の挙動の変更にあります。
Goランタイムは、src/pkg/runtime/signals_linux.h
ファイル内のSigTab
構造体配列を使用して、各シグナルに対するデフォルトのハンドラ設定を定義しています。この配列は、シグナル番号に対応するエントリを持ち、そのシグナルがGoランタイムによってどのように処理されるべきかを指定します。
変更前のSigTab
では、シグナル32(SIGRTMIN
)のエントリがN
(_SIG_IGN
、つまりシグナルを無視する)として設定されていました。これは、GoランタイムがSIGRTMIN
を受け取った際に、そのシグナルを無視するように自身のハンドラを設定することを意味します。しかし、NPTLはpthread_cancel
のためにこのシグナルを使用しており、Goランタイムがこれを無視するように設定してしまうと、NPTLの内部処理が妨げられ、結果としてCライブラリがpthread_cancel
を呼び出した際に問題が発生していました。
このコミットでは、src/pkg/runtime/signals_linux.h
の以下の行が変更されています。
--- a/src/pkg/runtime/signals_linux.h
+++ b/src/pkg/runtime/signals_linux.h
@@ -41,7 +41,7 @@ SigTab runtime·sigtab[] = {
/* 29 */ N, "SIGIO: i/o now possible",
/* 30 */ N, "SIGPWR: power failure restart",
/* 31 */ N, "SIGSYS: bad system call",
- /* 32 */ N, "signal 32",
+ /* 32 */ 0, "signal 32", /* SIGCANCEL; see issue 6997 */
/* 33 */ 0, "signal 33", /* SIGSETXID; see issue 3871 */
/* 34 */ N, "signal 34",
/* 35 */ N, "signal 35",
ここで、シグナル32のエントリがN
から0
に変更されています。
N
(_SIG_IGN
) は、Goランタイムがそのシグナルを無視するようにハンドラを設定することを意味します。0
は、Goランタイムがそのシグナルに対するハンドラを設定しないことを意味します。つまり、システムデフォルトのハンドラ(この場合はNPTLが設定したハンドラ)がそのまま維持されます。
この変更により、GoランタイムはSIGRTMIN
に対するNPTLのハンドラを上書きしなくなり、pthread_cancel
が期待通りに機能するようになります。
また、このコミットには、この問題を再現し、修正を検証するためのテストケースが追加されています。
misc/cgo/test/issue6997_linux.c
:pthread_create
でスレッドを生成し、pthread_cancel
でそのスレッドをキャンセルするC言語のコードが含まれています。misc/cgo/test/issue6997_linux.go
: 上記のCコードをCgo経由で呼び出し、pthread_cancel
が成功するかどうかを検証するGoのテストコードが含まれています。このテストは、pthread_cancel
が成功し、スレッドがキャンセルされたことを確認します。
これらのテストファイルは、GoランタイムがNPTLのpthread_cancel
と正しく共存できるようになったことを保証するために重要です。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、以下のファイルに集中しています。
-
src/pkg/runtime/signals_linux.h
: GoランタイムがLinuxシステムでシグナルをどのように処理するかを定義するヘッダーファイルです。ここで、SIGRTMIN
(シグナル32)に対するGoランタイムのデフォルトのシグナルハンドラ設定が変更されました。--- a/src/pkg/runtime/signals_linux.h +++ b/src/pkg/runtime/signals_linux.h @@ -41,7 +41,7 @@ SigTab runtime·sigtab[] = { /* 29 */ N, "SIGIO: i/o now possible", /* 30 */ N, "SIGPWR: power failure restart", /* 31 */ N, "SIGSYS: bad system call", - /* 32 */ N, "signal 32", + /* 32 */ 0, "signal 32", /* SIGCANCEL; see issue 6997 */ /* 33 */ 0, "signal 33", /* SIGSETXID; see issue 3871 */ /* 34 */ N, "signal 34", /* 35 */ N, "signal 35",
-
misc/cgo/test/issue6997_linux.c
:pthread_cancel
の動作をテストするための新しいC言語ソースファイルです。// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include <pthread.h> #include <stdio.h> #include <unistd.h> static pthread_t thread; static void* threadfunc(void* dummy) { while(1) { sleep(1); } } int StartThread() { return pthread_create(&thread, NULL, &threadfunc, NULL); } int CancelThread() { void *r; pthread_cancel(thread); pthread_join(thread, &r); return (r == PTHREAD_CANCELED); }
-
misc/cgo/test/issue6997_linux.go
: 上記のCコードをCgo経由で呼び出し、pthread_cancel
のテストを行うための新しいGo言語テストファイルです。// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Test that pthread_cancel works as expected // (NPTL uses SIGRTMIN to implement thread cancellation) // See http://golang.org/issue/6997 package cgotest /* #cgo CFLAGS: -pthread #cgo LDFLAGS: -pthread extern int StartThread(); extern int CancelThread(); */ import "C" import "testing" import "time" func test6997(t *testing.T) { r := C.StartThread() if r != 0 { t.Error("pthread_create failed") } c := make(chan C.int) go func() { time.Sleep(500 * time.Millisecond) c <- C.CancelThread() }() select { case r = <-c: if r == 0 { t.Error("pthread finished but wasn't cancelled??") } case <-time.After(5 * time.Second): t.Error("hung in pthread_cancel/pthread_join") } }
-
misc/cgo/test/cgo_linux_test.go
: 新しいテストケースTest6997
を既存のテストスイートに追加するための変更です。--- a/misc/cgo/test/cgo_linux_test.go +++ b/misc/cgo/test/cgo_linux_test.go @@ -7,3 +7,4 @@ package cgotest import "testing" func TestSetgid(t *testing.T) { testSetgid(t) } +func Test6997(t *testing.T) { test6997(t) }
コアとなるコードの解説
このコミットの最も重要な変更は、src/pkg/runtime/signals_linux.h
ファイル内のSigTab
配列の定義です。
SigTab
は、Goランタイムが起動時にシステムシグナルハンドラを設定する際に参照するテーブルです。各エントリは、特定のシグナル番号(配列のインデックス)に対応し、そのシグナルに対するGoランタイムの挙動を決定します。
変更前:
/* 32 */ N, "signal 32",
ここでN
は_SIG_IGN
マクロに展開され、Goランタイムがこのシグナル(シグナル32、すなわちSIGRTMIN
)を「無視」するようにシグナルハンドラを設定することを意味していました。これは、GoランタイムがSIGRTMIN
を受け取った際に何もしないように、OSに指示していたことになります。しかし、NPTLはpthread_cancel
のためにこのシグナルを使用しており、Goがこれを無視するように設定すると、NPTLの内部処理が妨げられ、pthread_cancel
が機能しなくなっていました。
変更後:
/* 32 */ 0, "signal 32", /* SIGCANCEL; see issue 6997 */
N
が0
に変更されました。この0
は、Goランタイムがこのシグナルに対するシグナルハンドラを設定しないことを意味します。これにより、GoランタイムはSIGRTMIN
に対する既存のシグナルハンドラ(NPTLが設定したもの)を上書きせず、そのまま維持します。結果として、NPTLはpthread_cancel
のためにSIGRTMIN
を送信した際に、自身のハンドラが呼び出され、スレッドキャンセル処理が正しく行われるようになります。コメント/* SIGCANCEL; see issue 6997 */
は、この変更の理由と関連するIssue番号を明確に示しています。
この小さな変更が、GoとCgoを介したCライブラリの相互運用性における重要なバグを修正し、Goプログラムの堅牢性を向上させています。
追加されたテストケースは、この修正が正しく機能することを検証します。issue6997_linux.c
でCスレッドを生成し、issue6997_linux.go
でGoからそのスレッドをキャンセルしようとします。pthread_cancel
が成功し、スレッドが終了したことを確認することで、シグナル処理の競合が解消されたことを保証します。
関連リンク
- Go Issue 6997: https://github.com/golang/go/issues/6997
- Go CL 47540043: https://go-review.googlesource.com/47540043
参考にした情報源リンク
- POSIX Threads (pthreads) -
pthread_cancel
: https://man7.org/linux/man-pages/man3/pthread_cancel.3.html - Real-time signals: https://man7.org/linux/man-pages/man7/signal.7.html
- NPTL (Native POSIX Thread Library)
- Go runtime signal handling (general concepts)
- Cgo documentation: https://go.dev/blog/cgo
sigaction
system call: https://man7.org/linux/man-pages/man2/sigaction.2.html- Go source code for
signals_linux.h
(relevant versions around 2014)