[インデックス 12625] ファイルの概要
このコミットは、Goランタイムのcgo(CgoはGoプログラムがC言語のコードを呼び出すためのメカニズム)における、macOS (Darwin) 環境でのシグナルマスキングに関するバグ修正を目的としています。具体的には、pthread_create
呼び出しの前後でシグナルマスクを適切に設定し、スレッド作成時の競合状態や予期せぬシグナルハンドリングの問題を回避します。
コミット
commit 1fc9a17c7ea276ee80045dc8cc9411eb024cf8ea
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Mar 14 12:49:59 2012 +0900
runtime/cgo: darwin signal masking
Fixes #3101 (again).
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5825043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1fc9a17c7ea276ee80045dc8cc9411eb024cf8ea
元コミット内容
このコミットは、runtime/cgo
ディレクトリ内の gcc_darwin_386.c
および gcc_darwin_amd64.c
ファイルに対して行われた変更です。主な変更内容は、libcgo_sys_thread_start
関数内で pthread_create
を呼び出す前後にシグナルマスクを操作するコードを追加することです。
具体的には、以下の行が追加されています。
sigset_t ign, oset;
sigfillset(&ign);
sigprocmask(SIG_SETMASK, &ign, &oset);
sigprocmask(SIG_SETMASK, &oset, nil);
これらの変更は、pthread_create
が新しいスレッドを作成する際に、親スレッドのシグナルマスクが子スレッドに継承されるというPOSIXの動作を考慮し、予期せぬシグナルが新しいスレッドに配送されるのを防ぐためのものです。
変更の背景
このコミットは「Fixes #3101 (again)」と記載されており、以前にも同様の問題が報告され、修正が試みられたものの、完全に解決されていなかったことを示唆しています。Goのcgoメカニズムは、GoランタイムとCライブラリ間の相互運用を可能にしますが、異なる言語のランタイムが混在する環境では、シグナルハンドリングのような低レベルのシステムコールが複雑な問題を引き起こすことがあります。
特に、macOS (Darwin) 環境では、シグナル配送のセマンティクスが他のUnix系システムと微妙に異なる場合があり、pthread_create
が新しいスレッドを作成する際に、親スレッドのシグナルマスクが子スレッドに継承されることが問題の原因となることがあります。もし親スレッドが特定のシグナルをブロックしている場合、新しく作成されたスレッドもそのシグナルをブロックした状態で開始され、予期せぬ動作やデッドロックを引き起こす可能性があります。
この修正は、pthread_create
の呼び出し中にすべてのシグナルを一時的にブロックし、スレッド作成後に元のシグナルマスクに戻すことで、この問題を解決しようとしています。これにより、新しいスレッドがクリーンなシグナルマスクで開始され、シグナル関連の競合状態が回避されます。
前提知識の解説
Cgo
Cgoは、GoプログラムがC言語のコードを呼び出すためのGoの機能です。GoのコードからCの関数を呼び出したり、CのコードからGoの関数を呼び出したりすることができます。これは、既存のCライブラリを利用したり、パフォーマンスが重要な部分をCで記述したりする場合に非常に便利です。しかし、GoとCのランタイムが混在するため、メモリ管理、スレッド、シグナルハンドリングなど、低レベルのシステムインタラクションにおいて複雑な問題が発生することがあります。
POSIXスレッド (pthreads)
POSIXスレッド(pthreads)は、Unix系オペレーティングシステムにおけるスレッドAPIの標準です。pthread_create
は新しいスレッドを作成するための関数で、pthread_attr_t
構造体を使ってスレッドの属性(スタックサイズ、スケジューリングポリシーなど)を設定できます。重要な点として、POSIXスレッドの仕様では、新しく作成されたスレッドは親スレッドのシグナルマスクを継承すると定められています。
シグナルとシグナルマスキング
シグナルは、Unix系システムにおいてプロセス間通信やイベント通知のために使用されるソフトウェア割り込みの一種です。例えば、Ctrl+Cを押すと SIGINT
シグナルがプロセスに送られ、プロセスを終了させることができます。
シグナルマスクとは、プロセスまたはスレッドが現在ブロックしている(受け取らない)シグナルの集合です。シグナルがブロックされている間は、そのシグナルが配送されてもすぐに処理されず、ブロックが解除されるまで保留されます。
sigset_t
: シグナルセットを表すデータ型。sigfillset(sigset_t *set)
:set
が指すシグナルセットにすべてのシグナルを追加します。つまり、すべてのシグナルをブロックするように設定します。sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
: プロセスまたはスレッドのシグナルマスクを変更します。how
: シグナルマスクの変更方法を指定します。SIG_SETMASK
:set
で指定されたシグナルセットにシグナルマスクを設定します。SIG_BLOCK
:set
で指定されたシグナルを現在のシグナルマスクに追加します。SIG_UNBLOCK
:set
で指定されたシグナルを現在のシグナルマスクから削除します。
set
: 新しいシグナルマスクまたは追加/削除するシグナルを指定するシグナルセットへのポインタ。oldset
: 変更前のシグナルマスクを保存するためのシグナルセットへのポインタ。元のシグナルマスクを保存しておき、後で復元するために使用されます。
Darwin (macOS) 特有の考慮事項
macOS (Darwin) はBSDベースのUnix系OSであり、そのカーネル(XNU)はMachマイクロカーネルとBSD層のハイブリッドです。シグナルハンドリングの動作はPOSIX標準に準拠していますが、実装の詳細や特定の条件下での挙動が他のLinuxなどのシステムと異なる場合があります。特に、スレッド作成時のシグナルマスクの継承は、競合状態やデッドロックの潜在的な原因となることがあります。
技術的詳細
このコミットの技術的詳細を掘り下げると、libcgo_sys_thread_start
関数は、GoランタイムがCgoを介して新しいOSスレッドを作成する際に呼び出される関数です。この関数内で pthread_create
が使用され、新しいスレッドが生成されます。
問題は、pthread_create
が呼び出される際に、呼び出し元のスレッド(親スレッド)のシグナルマスクが新しいスレッド(子スレッド)に継承されるというPOSIXのセマンティクスにあります。もし親スレッドが特定のシグナルをブロックしている場合、子スレッドもそのシグナルをブロックした状態で開始されます。
このコミットの修正は、以下のステップでこの問題を解決します。
- 現在のシグナルマスクの保存:
sigprocmask(SIG_SETMASK, &ign, &oset);
の呼び出しで、pthread_create
を呼び出す直前の現在のスレッドのシグナルマスクがoset
に保存されます。 - すべてのシグナルのブロック:
sigfillset(&ign);
でign
シグナルセットにすべてのシグナルが設定され、その後にsigprocmask(SIG_SETMASK, &ign, &oset);
が呼び出されることで、pthread_create
の呼び出し中はすべてのシグナルがブロックされます。これにより、新しいスレッドが作成される際に、親スレッドが一時的にすべてのシグナルをブロックしている状態となり、子スレッドもすべてのシグナルをブロックした状態で開始されます。 - スレッドの作成:
err = pthread_create(&p, &attr, threadentry, ts);
が実行され、新しいスレッドが作成されます。 - 元のシグナルマスクの復元:
sigprocmask(SIG_SETMASK, &oset, nil);
が呼び出されることで、pthread_create
の呼び出し前に保存しておいた元のシグナルマスクが復元されます。これにより、親スレッドは通常のシグナルハンドリングの状態に戻ります。
この一連の操作により、pthread_create
が実行されている間、親スレッドはシグナルによって中断されることなく、新しいスレッドが作成される際のシグナルマスクの継承も制御されます。新しいスレッドは、親スレッドが一時的にすべてのシグナルをブロックしている状態で作成されるため、予期せぬシグナルが新しいスレッドに配送されることを防ぎ、より予測可能な動作を保証します。
この修正は、特にGoランタイムがCgoを介して外部のCライブラリと連携し、それらのライブラリが独自のスレッドを作成したり、シグナルハンドリングを行ったりする場合に重要です。シグナルマスクの不適切な継承は、デッドロック、クラッシュ、または予期せぬプログラムの終了につながる可能性があります。
コアとなるコードの変更箇所
src/pkg/runtime/cgo/gcc_darwin_386.c
および src/pkg/runtime/cgo/gcc_darwin_amd64.c
の libcgo_sys_thread_start
関数。
--- a/src/pkg/runtime/cgo/gcc_darwin_386.c
+++ b/src/pkg/runtime/cgo/gcc_darwin_386.c
@@ -4,6 +4,7 @@
#include <string.h> /* for strerror */
#include <pthread.h>
+#include <signal.h>
#include "libcgo.h"
static void* threadentry(void*);
@@ -120,14 +121,21 @@ void
libcgo_sys_thread_start(ThreadStart *ts)
{
pthread_attr_t attr;
+ sigset_t ign, oset;
pthread_t p;
size_t size;
int err;
+ sigfillset(&ign);
+ sigprocmask(SIG_SETMASK, &ign, &oset);
+
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &size);
ts->g->stackguard = size;
err = pthread_create(&p, &attr, threadentry, ts);
+
+ sigprocmask(SIG_SETMASK, &oset, nil);
+
if (err != 0) {
fprintf(stderr, "runtime/cgo: pthread_create failed: %s\n", strerror(err));
abort();
gcc_darwin_amd64.c
も同様の変更が適用されています。
コアとなるコードの解説
追加されたコードは、pthread_create
の呼び出しをシグナルマスクの操作で囲んでいます。
#include <signal.h>
:sigset_t
,sigfillset
,sigprocmask
などのシグナル関連の関数や型を使用するために必要なヘッダーファイルです。sigset_t ign, oset;
:ign
はすべてのシグナルを含むシグナルセットとして使用され、oset
はsigprocmask
呼び出し前の元のシグナルマスクを保存するために使用されます。sigfillset(&ign);
:ign
シグナルセットにすべての標準シグナルを追加します。これにより、ign
は「すべてのシグナルをブロックする」という意図を表すセットになります。sigprocmask(SIG_SETMASK, &ign, &oset);
:SIG_SETMASK
: シグナルマスクを&ign
で指定されたセットに設定します。&ign
: すべてのシグナルをブロックするように設定します。&oset
: この呼び出しが実行される前の現在のシグナルマスクをoset
に保存します。 この行により、pthread_create
が呼び出される間、現在のスレッドはすべてのシグナルをブロックする状態になります。
err = pthread_create(&p, &attr, threadentry, ts);
: 新しいスレッドを作成します。この時点で、親スレッドはすべてのシグナルをブロックしているため、子スレッドもそのシグナルマスクを継承します。sigprocmask(SIG_SETMASK, &oset, nil);
:SIG_SETMASK
: シグナルマスクを&oset
で指定されたセットに設定します。&oset
:pthread_create
呼び出し前に保存しておいた元のシグナルマスクを復元します。nil
: 復元後のシグナルマスクを保存する必要がないためnil
を指定します。 この行により、親スレッドのシグナルマスクが元の状態に戻されます。
このシーケンスは、pthread_create
の呼び出し中にシグナルが配送されることによる競合状態を防ぎ、また、新しく作成されるスレッドが予期せぬシグナルマスクを継承することを防ぐための、堅牢な方法を提供します。
関連リンク
- Go issue #3101 (元の問題報告): このコミットが修正しようとしている具体的な問題の詳細が記載されているはずです。GoのIssue Trackerで検索することで見つかる可能性があります。
- Go CL 5825043: このコミットに対応するGoのコードレビューシステム (Gerrit) のチェンジリストです。より詳細な議論や以前の修正試行に関する情報が含まれている可能性があります。
参考にした情報源リンク
- POSIX
pthread_create
man page: スレッド作成時のシグナルマスク継承に関する詳細。 - POSIX
sigprocmask
man page: シグナルマスク操作に関する詳細。 - Go言語のcgoに関するドキュメント: cgoの仕組みと、C言語との相互運用における注意点。
- Darwin (macOS) のシグナルハンドリングに関するシステムプログラミングガイド。
- 以前のGoのコミット履歴や関連するバグ報告: 「(again)」という記述から、過去に同様の問題が議論された形跡があるため、それらの情報も参考になります。
- https://golang.org/cl/5825043 (Go Code Review)
- https://github.com/golang/go/commit/1fc9a17c7ea276ee80045dc8cc9411eb024cf8ea (GitHub Commit)