イベント関連のOSS libevent紹介、利用の際に時に発生した問題まとめ

イベント関連のライブラリを作るにあたり、select, epollどちらを使おうかなと探していると、libeventというライブラリを発見。サクッと使えて多機能だったので紹介します。

## **ざっくり概要**

epollやkqueue, select等沢山あるイベント監視系のAPIをラップし、シンプルに使えるようにしたイベント監視ライブラリ。マルチプラットフォーム対応になっています。

また、派生機能なのかはわからないですが、http通信用API等も備わっていて、大分多機能なライブラリとなっています。
[こちらの記事](http://neineigh.hatenablog.com/entry/2013/09/14/204723)によると、Chromeやntpdなんかでも利用されているそうです！

今回はイベント監視ライブラリ部分を紹介します。

## **動作環境**

Ubuntu 18.04 Desktop

## **インストール**

通常のaptコマンドでインストールできます。

```
sudo apt install libevent-dev
```

/usr配下に色々インストールされます。

公式のサンプルコードは以下やgithub公式に置いてあります。
/usr/share/doc/libevent-dev/examples/
or
https://github.com/libevent/libevent

## **サンプルコード**

サンプルコードのevent-read-fifo.cを参考に、こんな流れのサンプルにしました。

1. main内、`event_base_new`, `event_new`, `event_add`でbaseに対してイベント登録。
2. main_thread内で`event_base_dispatch`のmainループを起動(スレッドはブロックされます。)
3. mainから登録済みのFDへメッセージを送ると、`event_base_dispatch`内のイベント検知が発生し、登録してあった`sock_read`が呼ばれます。
4. ループを終わらせたい時は`event_base_loopbreak`を実行。

`event_new/event_add`もしくは`event_init/event_set/event_add`でさらにFDの追加も可能。
また、event_base_dispatchでは、最後の引数で呼び出される関数sock_readの引数argを好きに指定できます。

`event_dispatch`というAPIを利用すると監視もバックグラウンドでやってくれるようですが、今回の用途としては監視の開始/終了をこちらでコントロールしたいので`event_base_dispatch`を採用予定。

```C:main.c
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <event2/event-config.h>
#include <event2/event.h>

static void sock_read(evutil_socket_t fd, short event, void *arg)
{
        char buf[255];
        memset(buf, 0, sizeof(buf));
        read(fd, buf, sizeof(buf) - 1);
        printf("%s\n", buf);
        if(strcmp(buf, "exit") == 0) {
                event_base_loopbreak((struct event_base*)arg);
        }
}

void * main_thread(void *arg) {
        struct event_base* base = (struct event_base*)arg;
        event_base_dispatch(base);
        return NULL;
}

int main() {
        pthread_t tid;
        int sockpair[2];
        socketpair(AF_UNIX, SOCK_DGRAM, 0, sockpair);

        struct event_base* base = event_base_new();
        struct event *evfifo = event_new(base, sockpair[1], EV_READ|EV_PERSIST, sock_read, base);
        event_add(evfifo, NULL);
        printf("test before dispatch\n");
        pthread_create(&tid, NULL, main_thread, base);
        write(sockpair[0], "test", strlen("test"));
        write(sockpair[0], "file", strlen("file"));
        write(sockpair[0], "finished", strlen("finished"));
        write(sockpair[0], "exit", strlen("exit"));
        ///...
        pthread_join(tid, NULL);
        return 0;
}
```

実行するとこんな感じ。このサンプルでは"exit"を受け取った時点でループ終了となります。

```
$ ./a.out
test before dispatch
test
file
finished
exit
```

## **ライセンス**

ファイルによって微妙に違うようですが、BSD, OpenBSD, MITの3種類が記載されていました(公式githubより)。
どれも縛りの緩いものなので安心して利用出来そう。

## **利用の際に発生した問題**

1. `event_base_dispatch(base);`利用中に削除・追加したイベントの反映はすぐにされないことがあります。その際は`event_base_loopcontinue`を利用するとイベントをリロードしてくれる。(意外と反映が遅い？)
2. `event_assign`という関数がイベントのdelete && addをしてくれそうな説明文があったが、実際は出来ませんでした。update時は素直にdel⇒new⇒addしましょう。
3. 何かlibevent側でエラーが発生している場合は、`event_enable_debug_mode()`を使うとデバッグログが出てエラー箇所で止まります。(ただしevent_base作成前に実行すること)


- `event_enable_debug_mode()`を利用して確認したエラーの紹介。

`event_enable_debug_mode()`を2回呼んだ際に発生。

```console
[err] event_enable_debug_mode was called twice!
```

`event_assign`時に発生。add設定されたイベントの差し替えには使えません。残念。

```console
[err] event_assign called on an already added event 0x7f2680000c30 (events: 0x12, fd: 64, flags: 0x82)
```

`event_base_free`時に発生。根本原因までは追えていませんが、eventに絡んだデータのfree漏れがあると発生するようです。
valgrind --leak-check=full実行時に指摘されたリーク部分を修正することで発生しなくなりました。
2018/06/16 追記
⇒こちらイベント待ち受け終了とイベントハンドルが被った場合に発生していました。
終了の仕方については別途纏めます。

```console
[err] event_del_nolock_: noting a del on a non-setup event 0x55fcb1ff4e30 (events: 0x12, fd: 44, flags: 0x80)
```

## **終了の仕方について**

ヘッダーの英語をざっくり読んだ感じと動作を見比べた結果、以下のような仕様となりそうです。

|API|意味合い|備考|
|:---|:---|:---|
|event_base_loopbreak|発行されたイベントの処理終了を一通り待ってからloopを終了する。|入れ違いでのイベント発生することもある。|
|event_base_loopexit|発行されたイベントの処理終了を待たずに指定時間後に強制終了する。|イベントはおそらく発生(未確認)|
|event_base_got_break|event_base_loopbreakで終了したかを確認する。まだ処理終了していないイベントがある場合はtrueが返る。||
|event_base_got_exit|event_base_loopexitによる強制終了が発生したかを確認する。||

それぞれの仕様を照らし合わせると、`event_base_dispatch`を利用したループの場合は以下のようにすると安全に終了できそうです。

1. event_base_loopbreakを利用してloopを終了
2. event_base_dispatch後処理で、event_base_loopbreakとの入れ違いイベントがあるかもしれないので、`event_base_got_break`がtrueか確認
3. event_base_got_breakがtrueならもう一度`event_base_loopbreak`を実行してイベントの処理終了を再度待つ

コードとしてはこんな感じになるかと

```C:event_base_dispatchの終了処理
        event_base_dispatch(event_base);

        if(event_base_got_break(event_base)) {
                /*remain event callback, wait to call it*/
                event_base_loopbreak(event_base);
        }
```

## **参考**

概要として一番参考になったサイト
[libeventとは](http://neineigh.hatenablog.com/entry/2013/09/14/204723)
主な参考
[libeventの使い方](http://neineigh.hatenablog.com/entry/2013/09/29/175427)
[github](https://github.com/libevent/libevent/tree/master/) (sampleフォルダのサンプル、testフォルダのコードおよびinclude内のAPI説明コメント)
[libevent + 使い方](http://log.utahta.com/blog/2008/02/13/libevent-%E4%BD%BF%E3%81%84%E6%96%B9/)