stone (従来は select を使用) の epoll 対応の続き。
typedef struct _Pair { int common; struct _Pair *pair; ... SOCKET sd; /* socket descriptor */ } Pair; ... struct epoll_event ev; ev.events = EPOLLONESHOT; ev.data.ptr = pair; epoll_ctl(ePollFd, EPOLL_CTL_ADD, pair->sd, &ev);
といった感じで、 ePollFd にソケットディスクリプタを Pair 構造体と一緒に登録する。 そして、epoll_wait でイベント発生を待つ。
struct epoll_event evs[EVSMAX]; ... ret = epoll_wait(ePollFd, evs, EVSMAX, timeout);
配列 evs には、 発生したイベントが epoll_ctl で登録した構造体とともに 格納されるはずだが、
common = *(int*)evs[i].data.ptr;
などと構造体のデータを取り出そうとすると、 Segmentation violation が発生することがある。 しかも、デバッグ環境では正常に動くのに、 GCD のゲートウェイ上の設定環境で動かすと、 数分程度で Segmentation violation が発生してしまう。
なぜ期待したデータが得られないのかと、 stone のソースコード全体を見直すことに... しかし問題は見つからず。 epoll の仕様に見落としている点があるのかと思って マニュアルを読み直したり、 挙句の果てに、 Linux のカーネルの epoll 周辺のソースコードをながめたりした。
ptr の値が期待したものと異なる場合があるのかと思って、 epoll_ctl で登録したポインタと、 epoll_wait で取り出したポインタの値を表示させてみると、 予想に反してポインタのアドレス自体は正常のようだ。
早朝、このような stone のデバッグをやっていたのだが、 出社の時間が迫っていたので、作業を中断して出かける。
で、青山一丁目の駅を出て、六本木方面へ歩く途中、ふと気づいた。
epoll_ctl でポインタの登録を行ったプロセスと、 epoll_wait でポインタを取り出したプロセスが異なるのではないか? そういえば、デバッグ環境はシングルプロセスで実行し、 ゲートウェイの環境では複数プロセスで実行していた。
ポインタのアドレス自体が正常でも、 ポインタが指し示す実体がそのプロセス空間に存在しなければ、 当然 Segmentation violation が発生したり、 たまたま指し示した先のメモリにアクセスできたとしても、 期待しないデータが得られてしまうだろう。
会社に到着して、 さっそく ePollFd をプロセスごとに生成するように変更してみた。 これで、epoll_ctl するプロセスと、 epoll_wait するプロセスが常に同じになるはず。
で、GCD ゲートウェイ上の stone を入れ替えてみると、 正常に動いてしまった。 ハマるバグほど見つかってしまえば単純な原因であることが多いが、 epoll を使ったのが初めてということで epoll に集中しすぎたのが敗因と反省。
今回の場合、select を epoll で置き換えたさい、 select の fdset は、fork する前に作ればよかったので、 epoll の fd も、fork する前に open してしまっていた。 fdset はユーザ空間なのでプロセス間で共有されないが、 fork する前に open した fd は、プロセス間で共有される。