|
Document of c-modernization-kit (porter) 1.0.0
|
porter ライブラリの Linux 環境におけるシグナル取り扱いの仕様と、利用者に期待する振る舞いをまとめる。
porter ライブラリはシグナルに干渉しない。
| 項目 | porter の振る舞い |
|---|---|
| シグナルハンドラの登録 | 行わない |
| シグナルのブロック・マスク | 行わない |
| シグナルの送信 | 行わない |
| SA_RESTART / sigprocmask の操作 | 行わない |
ライブラリ内部のスレッド同期は POSIX シグナルではなく条件変数 (pthread_cond_timedwait) で実装されており、シグナルの配信に依存しない。これは Windows との共通実装を保つための設計による。
シグナルハンドラの登録・管理はすべて利用者の責務となる。
TCP 接続 (POTR_TYPE_TCP / POTR_TYPE_TCP_BIDIR) を使用している場合、相手が接続を切断した直後に porter 経由でデータを送信すると、OS が SIGPIPE を配信することがある。SIGPIPE のデフォルト動作はプロセス終了であり、porter はこれを抑制しない。
利用者は potrOpenServiceFromConfig() / potrOpenService() の前に以下のいずれかの対策を実施すること。
方法 A が最も簡便。TCP の切断はエラーとして potrSend() の戻り値 (POTR_ERROR) で検知できるため、SIGPIPE によるプロセス終了を避けて戻り値ベースのエラーハンドリングに統一できる。
UDP のみ使用する場合 (POTR_TYPE_UNICAST / POTR_TYPE_MULTICAST / POTR_TYPE_BROADCAST) は SIGPIPE は発生しない。
potrCloseService() は非同期シグナル安全ではない (内部でミューテックスを使用する)。**シグナルハンドラ内から potrCloseService() を呼んではならない**。
正しい終了シーケンスは以下の通り。
フラグ変数は volatile sig_atomic_t で宣言すること。volatile を省略するとコンパイラがレジスタ最適化でメモリへの書き込みを省略し、メインループがフラグの変化を検出できなくなる場合がある。
シグナルハンドラはプロセス内の任意の箇所に割り込むため、ハンドラ内から呼べる関数は async-signal-safe なものに限定される。
| 関数 | 安全性 |
|---|---|
| volatile sig_atomic_t への代入 | 安全 (アトミック保証) |
| _exit() | 安全 |
| write() | 安全 |
| close() | 安全 |
| waitpid() | 安全 |
| potrCloseService() などのライブラリ関数 | 非安全(ミューテックスを含む) |
| printf() / malloc() / free() | 非安全(内部でロックを取得) |
ハンドラ内では最小限の操作 (フラグのセット、安全なファイルディスクリプタ操作) のみを行い、後処理はメインループに委ねること。
porter は potrOpenService() / potrOpenServiceFromConfig() の呼び出し時に内部スレッド (送信・受信・接続管理・ヘルスチェック) を生成する。
Linux では、プロセスに届くシグナルはシグナルをマスクしていないスレッドのいずれかに配信される。porter の内部スレッドはシグナルをマスクしないため、アプリケーションが意図したシグナル (SIGINT など) が内部スレッドに配信される可能性がある。
内部スレッドがシグナルを受け取った場合の影響:
ただし、シグナルをメインスレッドで確実に受け取りたい場合は、potrOpenServiceFromConfig() / potrOpenService() の前に pthread_sigmask() で内部スレッドに継承させたくないシグナルをマスクしておくこと。子スレッドは親スレッドのシグナルマスクを引き継ぐ。
以下のシグナルはデフォルト動作 (プロセス終了またはコアダンプ) のまま porter に届く可能性がある。用途に応じてアプリケーション側でハンドラを設定すること。
| シグナル | デフォルト動作 | 留意事項 |
|---|---|---|
| SIGPIPE | プロセス終了 | TCP 使用時は要対策 (上述) |
| SIGUSR1 / SIGUSR2 | プロセス終了 | 外部から kill コマンドで誤送信された場合に即終了する |
| SIGALRM | プロセス終了 | alarm() を使うサードパーティライブラリとの組み合わせで発生し得る |
| SIGHUP | プロセス終了 | 端末切断時に発生。デーモン化する場合は SIG_IGN か設定リロードに使用するのが一般的 |
シグナルが配信されると、そのスレッドでブロッキング状態にあるシステムコールは中断して -1 / errno=EINTR を返す。 プロセス終了を引き起こさないシグナル (カスタムハンドラが登録済みのもの、または SIG_IGN に設定済みのもの) でも同様に中断が発生する。
porter の内部スレッドは recvfrom() / sendto() / connect() / recv() / send() などのブロッキングシステムコールを使用する。これらがシグナルによって EINTR で中断された場合でも、内部スレッドは EINTR を検出して呼び出しを再試行する設計になっており、通信処理は継続される。
利用者のコード (コールバック関数、メインループなど) でブロッキングシステムコールを使用している場合も、シグナルによる EINTR 中断が発生することに注意する。EINTR のハンドリングは利用者の責任である。
sigaction() でシグナルハンドラを登録する際に SA_RESTART フラグを指定すると、そのシグナルによって中断されたシステムコールはカーネルが自動的に再試行する。利用者コードで EINTR を個別にハンドルする手間を省ける。
ただし、SA_RESTART がシステムコールの自動再試行を保証するのは一部の呼び出しに限られる。pause() や select() / poll() / epoll_wait() など待機系の呼び出しは SA_RESTART を指定しても EINTR を返すことがある (POSIX 規定の適用外)。これらを使用する場合は EINTR を明示的にハンドルすること。
porter 内部スレッドは SA_RESTART の有無にかかわらず EINTR を処理する実装になっているため、利用者が SA_RESTART を設定するかどうかは porter の動作に影響しない。