|
|
nono 1.5.0
//
// nono
// Copyright (C) 2021 nono project
// Licensed under nono-license.txt
//
//
// シグナルスレッド
//
// このプロセス内で処理が必要な UNIX シグナルは一旦このスレッドで受け取り、
// 必要に応じてスレッドに通知などをする。
//
// スレッド作成前に StartThread() にてプロセス全体で該当シグナルをブロック
// する。他のスレッドはこの後生成するので、全スレッドがこの特性を引き継ぐ。
//
// シグナルスレッドは、該当シグナルをブロックしたままシグナルが発生するのを
// sigwait(3) で待つ (ブロックはデフォルト動作やハンドラ呼び出しが起きるのを
// ブロックするがペンディングにはなる、sigwait(3) はこのペンディングに反応
// する)。
//
// o SIGTERM、SIGINT(^C) を受け取ると UIMessage でメインスレッドに通知し、
// そこから安全にプロセスを終了させる。
//
// o SIGHUP、SIGPIPE、SIGUSR1、SIGUSR2 はデフォルトがプロセス終了なので
// ブロックして無視する。
//
// Linux (Ubuntu 20.04) ではシグナルの配送が POSIX とは異なっているらしく、
// プロセスに届いたシグナルを sigwait しているスレッドに配送してくれない。
// glibc のマニュアルには記載があるようだ。
// https://linuxjm.osdn.jp/html/glibc-linuxthreads/man3/pthread_kill.3.html
// Linux のマニュアルでは確認できなかった。
#include "signalthread.h"
#include "mainapp.h"
#include "uimessage.h"
#include <csignal>
static void signal_handler(int signo);
static void
signal_handler(int signo)
{
#if defined(__linux__)
// Linux ではプロセスに届いたシグナルを自動でスレッドに配送して
// くれないので、自前でやる必要があるようだ。
SignalThread *sigthread = GetSignalThread();
if (sigthread) {
sigthread->Kill(signo);
}
#endif
}
// コンストラクタ
SignalThread::SignalThread()
: inherited(OBJ_SIGNAL_THREAD)
{
sigemptyset(&waitset);
}
// デストラクタ
SignalThread::~SignalThread()
{
TerminateThread();
}
// 常にブロックするシグナル集合
static std::vector<int> siglist_always = {
SIGHUP, // ignore
SIGINT, // プロセス終了
SIGPIPE, // ignore
SIGTERM, // プロセス終了
SIGUSR1, // ignore
SIGUSR2, // ignore
};
// スレッド開始 (override)
//
// SIGINT(^C) など緊急性の高いシグナルのブロック期間を極力短くするため、
// シグナルのブロック(マスク)は、極力スレッドを開始する直前に行いたい。
// SignalThread は Device リストの先頭なので (see ../vm/vm.cpp)、この
// StartThread() 時点ではまだ (少なくとも自分が関与できる) 他のスレッドは
// 生成されておらず、ここでのこのスレッドに対する Mask() は、この後生成
// されるスレッドに引き継がれるベースになる。
bool
SignalThread::StartThread()
{
for (auto signo : siglist_always) {
Mask(SIG_BLOCK, signo);
}
// で、スレッドを開始。
return inherited::StartThread();
}
// スレッド実行
void
SignalThread::ThreadRun()
{
SetThreadAffinityHint(AffinityClass::Light);
request_terminate = false;
for (;;) {
int signo;
int r;
// sigwait() は成功なら 0、失敗ならエラーコードを返す。
putmsg(2, "sigwait...");
r = sigwait(&waitset, &signo);
// 終了要求が立っていたら、どのシグナルでも、エラーが起きてても
// とにかく先にスレッドを終了する。
if (request_terminate) {
break;
}
if (r != 0) {
if (r == EINTR) {
continue;
}
putmsg(0, "sigwait: %s", strerror(r));
// XXX どうする?
continue;
}
putmsg(1, "sigwait got %d", signo);
// ここからシグナル別の処理
switch (signo) {
case SIGTERM:
case SIGINT:
// メインスレッドから終了してもらう
gMainApp.GetUIMessage()->Post(UIMessage::APPEXIT);
break;
default: // 他は無視する
break;
}
}
putmsg(1, "ThreadRun terminating");
}
void
SignalThread::Terminate()
{
putmsg(1, "Terminate");
// フラグを立ててシグナルで通知
request_terminate = true;
Kill(SIGTERM);
}
// SignalThread にシグナルを投げる (他スレッドからも使用可)
void
SignalThread::Kill(int signo)
{
assert((bool)thread);
putmsg(1, "Kill(%d)", signo);
pthread_kill(thread->native_handle(), signo);
}
// SIG_BLOCK ならブロックする集合に追加 (=配送する)。
// SIG_UNBLOCK ならブロックする集合から削除 (=こちらで処理する)。
void
SignalThread::Mask(int how, int signo)
{
const char *howstr = (how == SIG_BLOCK) ? "BLOCK" : "UNBLOCK";
putmsg(1, "Mask(%s, %d)", howstr, signo);
// シグナルハンドラを設定 (シグナルハンドラはプロセスグローバル)
// signal(3) は挙動が違う可能性があるので使わないこと。
struct sigaction act;
memset(&act, 0, sizeof(act));
if (how == SIG_BLOCK) {
act.sa_handler = signal_handler;
} else {
act.sa_handler = SIG_DFL;
}
if (sigaction(signo, &act, NULL) < 0) {
putmsg(0, "sigaction(%d, %s): %s", signo, howstr, strerror(errno));
return;
}
// このスレッドのマスクを設定
sigset_t set;
sigemptyset(&set);
sigaddset(&set, signo);
if (pthread_sigmask(how, &set, NULL) < 0) {
putmsg(0, "pthread_sigmask(%s, %d): %s",
howstr, signo, strerror(errno));
return;
}
// sigwait() 用のセットを更新
if (how == SIG_BLOCK) {
sigaddset(&waitset, signo);
} else {
sigdelset(&waitset, signo);
}
// 適当デバッグ表示
if (loglevel >= 1) {
std::string str;
#define CHECK(NAM) do { \
if (sigismember(&waitset, __CONCAT(SIG,NAM))) { \
str += " "; \
str += #NAM; \
} \
} while (0)
CHECK(HUP);
CHECK(INT);
CHECK(PIPE);
CHECK(TERM);
CHECK(USR1);
CHECK(USR2);
putmsgn("waitset =%s", str.c_str());
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.