シグナルをハンドルする(2) sigwait/sigwaitinfo/sigtimedwait

# 導入

シグナルシリーズ第2弾です。目次は [こちら](https://qiita.com/AAsada/items/13b00ad76ca757d762d6)

シグナルハンドラでは制限が多い(非同期安全だったり変数の制限だったり)ので、
使いやすい方法がよいということで、sigwait。

# 動作確認環境

(変更ありませんが再掲)

Host OS: Windows10 Pro 20H2
Guest OS: WSL2 Ubuntu-20.04
Compiler: g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

# sigsetops

sigset_t という型が必要なのでまずはそちらの制御方法。
とはいえ、そんなに複雑ではないのでman pageのLinkのみで。

man page: [sigsetops(3)](https://linuxjm.osdn.jp/html/LDP_man-pages/man3/sigsetops.3.html)

# sigprocmask

sigwaitしていない間にシグナル受信するとデフォルト動作(SIGINTなら落ちる)となるのでブロックします。

man page: [sigprocmask(2)](https://linuxjm.osdn.jp/html/LDP_man-pages/man2/sigprocmask.2.html)

# sigwait

専用のスレッドを用意することになりますが、通常のコンテキストでハンドルできるので、
非同期安全な関数とか気にせずになんでもできます。
man page: [sigwait(3)](https://linuxjm.osdn.jp/html/LDP_man-pages/man3/sigwait.3.html)

<details><summary>動作確認コード</summary><div>

```c++:sigwait.cpp
#include <stdio.h>
#include <stdlib.h>
#include <signal.h> // sigwait

#define LOG(STR, ...) printf("%s " STR "\n", __func__, ## __VA_ARGS__)

int main() {
    LOG("start");

    sigset_t ss;
    sigemptyset(&ss); // 初期化
    if( sigaddset(&ss, SIGINT) != 0 ) {
        LOG("err sigaddset");
        return 1;
    }

    // マスクする
    if( sigprocmask(SIG_BLOCK, &ss, nullptr) != 0 ) {
        LOG("err sigprocmask");
        return 1;
    }

    // シグナル待ち
    int cnt = 0;
    while(cnt < 3) {
        int signo;
        if( sigwait(&ss, &signo) == 0 ) {
            LOG("signal %d", signo);
        } else {
            LOG("err sigwait");
        }
        cnt++;
    }
    LOG("end");
    return 0;
}
```

</div></details>

# sigwaitinfo

signal と sigaction のように sigwait にも拡張版があります。

man page: [sigwaitinfo(2)](https://linuxjm.osdn.jp/html/LDP_man-pages/man2/sigwaitinfo.2.html)


<details><summary>動作確認コード</summary><div>

```c++:sigwaitinfo.cpp
#include <stdio.h>
#include <signal.h> // sigemptyset/sigaddset/sigprocmask/sigwaitinfo

#define LOG(STR, ...) printf("%s " STR "\n", __func__, ## __VA_ARGS__)

int main() {
    LOG("start");

    sigset_t ss;
    sigemptyset(&ss); // 初期化
    if( sigaddset(&ss, SIGINT) != 0 ) {
        LOG("err sigaddset");
        return 1;
    }

    // マスクする
    if( sigprocmask(SIG_BLOCK, &ss, nullptr) != 0 ) {
        LOG("err sigprocmask");
        return 1;
    }

    // シグナル待ち
    int cnt = 0;
    while(cnt < 3) {
        siginfo_t si;
        if( sigwaitinfo(&ss, &si) >= 0 ) {
            LOG("signal %d %d %d", si.si_signo, si.si_errno, si.si_code);
        } else {
            LOG("err sigwaitinfo");
        }
        cnt++;
    }
    LOG("end");
    return 0;
}
```

</div></details>

# sigtimedwait

sigwaitinfo のタイムアウト版が sigtimedwait です。
man page は sigwaitinfo と同じなので省略。

## (1) sigwaitinfo互換

timeout に nullptr を指定した場合、無限待ちになりました。
他のAPIでもよくある動作ですが、man page含め特に言及されていませんでしたので、仕様なのかは分かりませんでした。

<details><summary>動作確認コード</summary><div>

```c++:sigtimedwait_1.cpp
```

</div></details>

## (2) 指定時間待ち

時間を指定すればその時間内にシグナル受信しないとタイムアウトします。
ただし、ブロックする時間は指定時間以上となるだけで、厳密さはありません。

<details><summary>動作確認コード</summary><div>

```c++:sigtimedwait_2.cpp
#include <stdio.h>
#include <signal.h> // sigemptyset/sigaddset/sigprocmask/sigtimedwait
#include <errno.h> // errno

#define LOG(STR, ...) printf("%s " STR "\n", __func__, ## __VA_ARGS__)

int main() {
    LOG("start");

    sigset_t ss;
    sigemptyset(&ss); // 初期化
    if( sigaddset(&ss, SIGINT) != 0 ) {
        LOG("err sigaddset");
        return 1;
    }

    // マスクする
    if( sigprocmask(SIG_BLOCK, &ss, nullptr) != 0 ) {
        LOG("err sigprocmask");
        return 1;
    }


    // 待ち時間は200ms
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 200 * 1000 * 1000;
    
    // シグナル待ち
    int sigcnt = 0;
    int cnt = 0;
    char mark[5] = {'|', '/', '-', '\\'};
    while(sigcnt < 3) {
        siginfo_t si;
        if( sigtimedwait(&ss, &si, &ts) >= 0 ) {
            LOG("signal %d %d %d", si.si_signo, si.si_errno, si.si_code);
            sigcnt++;
        } else if( errno != EAGAIN ) {
            LOG("err sigtimedwait");
        }
        
        // アプリが止まっていないことの確認用
        printf("%c\r", mark[(cnt + 1) % 4]); fflush(stdout); cnt++;
    }
    LOG("end");
    return 0;
}
```

</div></details>

## (3) ブロックなし

ブロックなしにするには時間指定しなければよいです。
ただし、こちらも厳密さはありません。

見た目上はほぼ変わらない動作をしますが、当該スレッドで別作業(動作確認コードならusleep)も出来るところが異なります。

<details><summary>動作確認コード</summary><div>

```c++:sigtimedwait_3.cpp
#include <stdio.h>
#include <signal.h> // sigemptyset/sigaddset/sigprocmask/sigtimedwait
#include <errno.h> // errno
#include <unistd.h> // usleep

#define LOG(STR, ...) printf("%s " STR "\n", __func__, ## __VA_ARGS__)

int main() {
    LOG("start");

    sigset_t ss;
    sigemptyset(&ss); // 初期化
    if( sigaddset(&ss, SIGINT) != 0 ) {
        LOG("err sigaddset");
        return 1;
    }

    // マスクする
    if( sigprocmask(SIG_BLOCK, &ss, nullptr) != 0 ) {
        LOG("err sigprocmask");
        return 1;
    }


    // 待ち時間はなし
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 0;
    
    // シグナル待ち
    int sigcnt = 0;
    int cnt = 0;
    char mark[5] = {'|', '/', '-', '\\'};
    while(sigcnt < 3) {
        siginfo_t si;
        if( sigtimedwait(&ss, &si, &ts) >= 0 ) {
            LOG("signal %d %d %d", si.si_signo, si.si_errno, si.si_code);
            sigcnt++;
        } else if( errno != EAGAIN ) {
            LOG("err sigtimedwait");
        }
        
        // アプリが止まっていないことの確認用
        printf("%c\r", mark[(cnt + 1) % 4]); fflush(stdout); cnt++;
        usleep(200 * 1000);
    }
    LOG("end");
    return 0;
}
```

</div></details>