Annotation of nono/host/signalthread.cpp, revision 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.