LoginSignup
6
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-03

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

ざっくり概要

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

また、派生機能なのかはわからないですが、http通信用API等も備わっていて、大分多機能なライブラリとなっています。
こちらの記事によると、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を採用予定。

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回呼んだ際に発生。

[err] event_enable_debug_mode was called twice!

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

[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 追記
⇒こちらイベント待ち受け終了とイベントハンドルが被った場合に発生していました。
終了の仕方については別途纏めます。

[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を実行してイベントの処理終了を再度待つ

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

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とは
主な参考
libeventの使い方
github (sampleフォルダのサンプル、testフォルダのコードおよびinclude内のAPI説明コメント)
libevent + 使い方

6
3
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3