Annotation of nono/host/signalthread.cpp, revision 1.1.1.1

1.1       root        1: //
                      2: // nono
                      3: // Copyright (C) 2021 nono project
                      4: // Licensed under nono-license.txt
                      5: //
                      6: 
                      7: //
                      8: // シグナルスレッド
                      9: //
                     10: 
                     11: // このプロセス内で処理が必要な UNIX シグナルは一旦このスレッドで受け取り、
                     12: // 必要に応じてスレッドに通知などをする。
                     13: //
                     14: // スレッド作成前に StartThread() にてプロセス全体で該当シグナルをブロック
                     15: // する。他のスレッドはこの後生成するので、全スレッドがこの特性を引き継ぐ。
                     16: //
                     17: // シグナルスレッドは、該当シグナルをブロックしたままシグナルが発生するのを
                     18: // sigwait(3) で待つ (ブロックはデフォルト動作やハンドラ呼び出しが起きるのを
                     19: // ブロックするがペンディングにはなる、sigwait(3) はこのペンディングに反応
                     20: // する)。
                     21: //
                     22: // o SIGTERM を受け取ると UIMessage でメインスレッドに通知し、そこから安全に
                     23: //   プロセスを終了させる。
                     24: //
                     25: // o SIGINT(^C)、SIGTSTP(^Z)、SIGQUIT(^\) は HostCOM のうち stdio ドライバが
                     26: //   必要とするため動作を切り替える。HostCOM が stdio なら、これらのシグナル
                     27: //   は処理せず、HostCOM スレッドにパイプで通知する。
                     28: //   HostCOM が stdio 以外なら、これらのシグナルは HostCOM に通知せず、
                     29: //   こちらで処理する。
                     30: //
                     31: // o SIGHUP、SIGPIPE、SIGUSR1、SIGUSR2 はデフォルトがプロセス終了なので
                     32: //   ブロックして無視する。
                     33: //   このうち無難そうな SIGPIPE をこのスレッドへのメッセージ通知として使う。
                     34: //   SIGPIPE 発生時に特定のメンバ変数がセットされているとそれに従って
                     35: //   アクションを起こす。
                     36: //
                     37: // Linux (Ubuntu 20.04) ではシグナルの配送が POSIX とは異なっているらしく、
                     38: // プロセスに届いたシグナルを sigwait しているスレッドに配送してくれない。
                     39: // glibc のマニュアルには記載があるようだ。
                     40: // https://linuxjm.osdn.jp/html/glibc-linuxthreads/man3/pthread_kill.3.html
                     41: // Linux のマニュアルでは確認できなかった。
                     42: 
                     43: #include "signalthread.h"
                     44: #include "uimessage.h"
                     45: #include <climits>
                     46: #include <csignal>
                     47: 
                     48: // グローバル参照用
                     49: SignalThread *gSignalThread;
                     50: 
                     51: void signal_handler(int signo);
                     52: void
                     53: signal_handler(int signo)
                     54: {
                     55: #if defined(__linux__)
                     56:        // Linux ではプロセスに届いたシグナルを自動でスレッドに配送して
                     57:        // くれないので、自前でやる必要があるようだ。
                     58:        if (gSignalThread) {
                     59:                gSignalThread->Kill(signo);
                     60:        }
                     61: #endif
                     62: }
                     63: 
                     64: // コンストラクタ
                     65: SignalThread::SignalThread()
                     66:        : inherited("SignalThread")
                     67: {
                     68:        wpipe = -1;
                     69:        sigemptyset(&waitset);
                     70: }
                     71: 
                     72: // デストラクタ
                     73: SignalThread::~SignalThread()
                     74: {
                     75:        TerminateThread();
                     76:        gSignalThread = NULL;
                     77: }
                     78: 
                     79: // 常にブロックするシグナル集合
                     80: static std::vector<int> siglist_always = {
                     81:        SIGHUP,         // ignore
                     82:        SIGPIPE,        // ignore
                     83:        SIGTERM,        // プロセス終了
                     84:        SIGUSR1,        // ignore
                     85:        SIGUSR2,        // ignore
                     86: };
                     87: 
                     88: // HostCOM が受け取るかどうかによって動作を変えるシグナル集合
                     89: static std::vector<int> siglist_com = {
                     90:        SIGINT,         // HostCOM (^C)
                     91:        SIGQUIT,        // HostCOM (^\)
                     92:        SIGTSTP,        // HostCOM (^Z)
                     93: };
                     94: 
                     95: // スレッド開始 (override)
                     96: //
                     97: // SIGINT(^C) など緊急性の高いシグナルのブロック期間を極力短くするため、
                     98: // シグナルのブロック(マスク)は、極力スレッドを開始する直前に行いたい。
                     99: // SignalThread は Device リストの先頭なので (see ../vm/vm.cpp)、この
                    100: // StartThread() 時点ではまだ (少なくとも自分が関与できる) 他のスレッドは
                    101: // 生成されておらず、ここでのこのスレッドに対する Mask() は、この後生成
                    102: // されるスレッドに引き継がれるベースになる。
                    103: bool
                    104: SignalThread::StartThread()
                    105: {
                    106:        for (auto signo : siglist_always) {
                    107:                Mask(SIG_BLOCK, signo);
                    108:        }
                    109:        for (auto signo : siglist_com) {
                    110:                Mask(SIG_BLOCK, signo);
                    111:        }
                    112: 
                    113:        // で、スレッドを開始。
                    114:        return inherited::StartThread();
                    115: }
                    116: 
                    117: // スレッド実行
                    118: void
                    119: SignalThread::ThreadRun()
                    120: {
                    121:        request_terminate = false;
                    122:        request_wpipe = -1;
                    123: 
                    124:        for (;;) {
                    125:                int signo;
                    126:                int r;
                    127: 
                    128:                // sigwait() は成功なら 0、失敗ならエラーコードを返す。
                    129:                putmsg(2, "sigwait...");
                    130:                r = sigwait(&waitset, &signo);
                    131:                if (r != 0) {
                    132:                        if (r == EINTR) {
                    133:                                continue;
                    134:                        }
                    135:                        putmsg(0, "sigwait: %s", strerror(r));
                    136:                        // XXX どうする?
                    137:                        continue;
                    138:                }
                    139:                putmsg(1, "sigwait got %d", signo);
                    140: 
                    141:                // 終了要求が立っていたら、どのシグナルでもスレッドを終了する
                    142:                if (request_terminate) {
                    143:                        break;
                    144:                }
                    145: 
                    146:                // ここからシグナル別の処理
                    147:                switch (signo) {
                    148:                 case SIGPIPE:  // コマンド
                    149:                 {
                    150:                        // 本来は無視するだけなので、ついでにこのスレッドに対する
                    151:                        // メッセージ通知として使用する。フラグが立ってなければ単に無視。
                    152:                        //
                    153:                        // SIGPIPE 発生時に request_wpipe が非負なら、HostCOM からの
                    154:                        // パイプ接続もしくは切断の要求。
                    155:                        int req = request_wpipe.exchange(-1);
                    156:                        if (req >= 0) {
                    157:                                if (loglevel >= 1) {
                    158:                                        if (req == INT_MAX) {
                    159:                                                putmsg(1, "SIGPIPE: HostCOM del");
                    160:                                        } else {
                    161:                                                putmsg(1, "SIGPIPE: HostCOM add %d", req);
                    162:                                        }
                    163:                                }
                    164:                                // HostCOM スレッドへのシグナル通知を変更
                    165:                                ChangeCOM(req);
                    166:                        }
                    167:                        break;
                    168:                 }
                    169: 
                    170:                 case SIGTERM:  // SIGTERM なら即終了
                    171:                        // メインスレッドから終了してもらう
                    172:                        UIMessage::Post(UIMessage::APPEXIT);
                    173:                        break;
                    174: 
                    175:                 case SIGINT:   // パイプがあれば配送、なければここで処理
                    176:                        if (wpipe >= 0) {
                    177:                                WriteSignal(wpipe, signo);
                    178:                        } else {
                    179:                                // メインスレッドから終了してもらう
                    180:                                UIMessage::Post(UIMessage::APPEXIT);
                    181:                        }
                    182:                        break;
                    183: 
                    184:                 case SIGTSTP:  // パイプがあれば配送、なければデフォルト動作
                    185:                 case SIGQUIT:
                    186:                        if (wpipe >= 0) {
                    187:                                // パイプがあれば書き込んで通知。
                    188:                                WriteSignal(wpipe, signo);
                    189:                        } else {
                    190:                                // パイプがなければ、
                    191:                                // ここでデフォルト動作を起こしたい。
                    192: 
                    193:                                // 一旦解除して..
                    194:                                Mask(SIG_UNBLOCK, signo);
                    195: 
                    196:                                // シグナルを自身に送信。
                    197:                                Kill(signo);
                    198: 
                    199:                                // そして再度ブロックしておく。
                    200:                                // こうしないと、2回目の ^Z が効かないとかになる。
                    201:                                Mask(SIG_BLOCK, signo);
                    202:                        }
                    203:                        break;
                    204: 
                    205:                 default:               // 他は無視する
                    206:                        break;
                    207:                }
                    208:        }
                    209: 
                    210:        putmsg(1, "ThreadRun terminating");
                    211: }
                    212: 
                    213: void
                    214: SignalThread::Terminate()
                    215: {
                    216:        putmsg(1, "Terminate");
                    217: 
                    218:        // フラグを立ててシグナルで通知
                    219:        request_terminate = true;
                    220:        Kill(SIGPIPE);
                    221: }
                    222: 
                    223: // HostCOM スレッドへのパイプを接続する (HostCOM スレッドから呼ばれる)
                    224: void
                    225: SignalThread::AddPipe(int fd)
                    226: {
                    227:        putmsg(1, "AddPipe(fd=%d)", fd);
                    228: 
                    229:        if ((bool)thread == false) {
                    230:                // HostCOM から呼ばれるが、起動時の初期設定は Init() フェーズ内の
                    231:                // ため、その時点ではまだどちらもスレッドはない。
                    232:                // この場合は単にここで設定するだけでよい。
                    233:                ChangeCOM(fd);
                    234:        } else {
                    235:                // スレッド開始後なら、フラグを立ててシグナルで通知
                    236:                request_wpipe = fd;
                    237:                Kill(SIGPIPE);
                    238:        }
                    239: }
                    240: 
                    241: // HostCOM スレッドへのパイプを削除する
                    242: void
                    243: SignalThread::DeletePipe()
                    244: {
                    245:        putmsg(1, "DeletePipe(current=%d)", wpipe);
                    246: 
                    247:        // フラグを立ててシグナルで通知
                    248:        request_wpipe = INT_MAX;
                    249:        Kill(SIGPIPE);
                    250: }
                    251: 
                    252: // シグナルをパイプに再配送する
                    253: void
                    254: SignalThread::WriteSignal(int wfd, int signo)
                    255: {
                    256:        char buf[1];
                    257:        int r;
                    258: 
                    259:        buf[0] = signo;
                    260:        r = write(wpipe, buf, sizeof(buf));
                    261:        if (r < 0) {
                    262:                // エラーが起きたパイプは閉じればいいか…
                    263:                ChangeCOM(INT_MAX);
                    264:        }
                    265: }
                    266: 
                    267: // HostCOM スレッドとのパイプを接続/切断する。
                    268: // SignalThread 側処理。
                    269: void
                    270: SignalThread::ChangeCOM(int reqfd)
                    271: {
                    272:        // reqfd が INT_MAX なら現在のディスクリプタを切断。
                    273:        // そうでなければ、reqfd を接続。
                    274:        // そんな高頻度で投げないので運用上は問題にならないはず。
                    275: 
                    276:        putmsg(1, "ChangeCOM(current=%d request=%d)", wpipe, reqfd);
                    277: 
                    278:        if (reqfd == INT_MAX) {
                    279:                // パイプを切断してからシグナルのブロックを解除
                    280:                close(wpipe);
                    281:                wpipe = -1;
                    282: 
                    283:                for (auto signo : siglist_com) {
                    284:                        Mask(SIG_UNBLOCK, signo);
                    285:                }
                    286:        } else {
                    287:                // 必要なシグナルをブロックしてからパイプを接続
                    288:                for (auto signo : siglist_com) {
                    289:                        Mask(SIG_BLOCK, signo);
                    290:                }
                    291: 
                    292:                wpipe = reqfd;
                    293:        }
                    294: }
                    295: 
                    296: // SignalThread にシグナルを投げる (他スレッドからも使用可)
                    297: void
                    298: SignalThread::Kill(int signo)
                    299: {
                    300:        assert((bool)thread);
                    301: 
                    302:        putmsg(1, "Kill(%d)", signo);
                    303:        pthread_kill(thread->native_handle(), signo);
                    304: }
                    305: 
                    306: // SIG_BLOCK ならブロックする集合に追加 (=配送する)。
                    307: // SIG_UNBLOCK ならブロックする集合から削除 (=こちらで処理する)。
                    308: void
                    309: SignalThread::Mask(int how, int signo)
                    310: {
                    311:        const char *howstr = (how == SIG_BLOCK) ? "BLOCK" : "UNBLOCK";
                    312: 
                    313:        putmsg(1, "Mask(%s, %d)", howstr, signo);
                    314: 
                    315:        // シグナルハンドラを設定 (シグナルハンドラはプロセスグローバル)
                    316:        // signal(3) は挙動が違う可能性があるので使わないこと。
                    317:        struct sigaction act;
                    318:        memset(&act, 0, sizeof(act));
                    319:        if (how == SIG_BLOCK) {
                    320:                act.sa_handler = signal_handler;
                    321:        } else {
                    322:                act.sa_handler = SIG_DFL;
                    323:        }
                    324:        if (sigaction(signo, &act, NULL) < 0) {
                    325:                putmsg(0, "sigaction(%d, %s): %s", signo, howstr, strerror(errno));
                    326:                return;
                    327:        }
                    328: 
                    329:        // このスレッドのマスクを設定
                    330:        sigset_t set;
                    331:        sigemptyset(&set);
                    332:        sigaddset(&set, signo);
                    333:        if (pthread_sigmask(how, &set, NULL) < 0) {
                    334:                putmsg(0, "pthread_sigmask(%s, %d): %s",
                    335:                        howstr, signo, strerror(errno));
                    336:                return;
                    337:        }
                    338: 
                    339:        // sigwait() 用のセットを更新
                    340:        if (how == SIG_BLOCK) {
                    341:                sigaddset(&waitset, signo);
                    342:        } else {
                    343:                sigdelset(&waitset, signo);
                    344:        }
                    345: 
                    346:        // 適当デバッグ表示
                    347:        if (loglevel >= 1) {
                    348:                std::string str;
                    349: 
                    350: #define CHECK(NAM)     do {    \
                    351:        if (sigismember(&waitset, __CONCAT(SIG,NAM))) { \
                    352:                str += " ";             \
                    353:                str += #NAM;    \
                    354:        }       \
                    355: } while (0)
                    356:                CHECK(HUP);
                    357:                CHECK(INT);
                    358:                CHECK(QUIT);
                    359:                CHECK(PIPE);
                    360:                CHECK(TERM);
                    361:                CHECK(TSTP);
                    362:                CHECK(USR1);
                    363:                CHECK(USR2);
                    364: 
                    365:                putmsgn("waitset =%s", str.c_str());
                    366:        }
                    367: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.