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

[インデックス 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ライブラリの間のより堅牢な相互運用性を確保することを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念についての知識が必要です。

  1. NPTL (Native POSIX Thread Library): LinuxにおけるPOSIXスレッド(pthreads)の標準的な実装です。NPTLは、ユーザー空間でスレッドを管理し、カーネルの軽量プロセス(LWP)にマッピングすることで、効率的なマルチスレッドプログラミングを可能にします。pthread_cancelのようなスレッド操作は、NPTLによって提供されます。

  2. pthread_cancel: POSIXスレッドAPIの一部で、指定されたスレッドの実行を非同期的に終了させるための関数です。キャンセルされたスレッドは、キャンセルポイント(例: sleep, read, writeなどのシステムコール)に到達した際に終了するか、非同期キャンセルが有効な場合はいつでも終了します。NPTLは、このキャンセル処理を内部的にシグナルを用いて実現しています。

  3. リアルタイムシグナル (Real-time Signals): Linuxカーネルが提供するシグナルの一種で、従来の標準シグナル(SIGINT, SIGTERMなど)とは異なり、キューイングが可能で、シグナル番号の範囲が異なります。SIGRTMINからSIGRTMAXまでの範囲がリアルタイムシグナルとして利用可能です。NPTLは、これらのシグナルを内部的なスレッド管理(特にpthread_cancel)に利用することがあります。SIGRTMINは、システムによってその値が異なることがありますが、多くのLinuxシステムでは32番のシグナルとして定義されています。

  4. シグナルハンドラ: 特定のシグナルがプロセスに送信されたときに実行される関数です。プロセスはsigactionシステムコールなどを使用して、特定のシグナルに対するカスタムハンドラを設定できます。Goランタイムも、自身の内部処理のためにシグナルハンドラを設定します。

  5. Cgo: Go言語の機能の一つで、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりすることを可能にします。これにより、既存のCライブラリをGoプロジェクトで再利用したり、パフォーマンスが重要な部分をCで記述したりすることができます。Cgoを使用する際、GoランタイムとCライブラリの間のシグナル処理の相互作用が問題となることがあります。

  6. 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と正しく共存できるようになったことを保証するために重要です。

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

このコミットにおけるコアとなるコードの変更箇所は、以下のファイルに集中しています。

  1. 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",
    
  2. 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);
    }
    
  3. 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")
    	}
    }
    
  4. 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 */

N0に変更されました。この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が成功し、スレッドが終了したことを確認することで、シグナル処理の競合が解消されたことを保証します。

関連リンク

参考にした情報源リンク