epoll を使用して http 接続をリッスンおよび処理するアプリケーションがあります。epoll_wait() は fd でクローズ イベントを「行」で 2 回受け取ることがあります。意味: epoll_wait() は read()/recv() が 0 を返す接続 fd を返す(socket) は、初回はクローズとして検出されます。2回目はクラッシュ。
この問題は、実際の使用ではめったに発生しません (サーバーごとに実際に約 500 ~ 1000 人のユーザーがいる 1 つのサイトを除く)。1秒あたり1000を超える同時接続でhttp siegeを使用して問題を再現できます。この場合、アプリケーションは (無効なポインタが原因で) 非常にランダムに、時には数秒後、通常は数十分後にセグメンテーション違反を起こします。1 秒あたりの接続数を減らして問題を再現できましたが、そのためにはアプリケーションを長時間、何日も、何週間も実行する必要があります。
すべての新しい accept() 接続 fd:s は非ブロッキングとして設定され、ワンショット、エッジ トリガーとして epoll に追加され、read() が使用可能になるのを待ちます。では、サーバーの負荷が高い場合、epoll はアプリケーションが close-event を取得できなかったと判断し、新しいイベントをキューに入れるのはなぜでしょうか?
epoll_wait() は独自のスレッドで実行され、別の場所で処理される fd イベントをキューに入れます。epoll から同じ fd にイベントが 2 回続けて発生するかどうかをチェックする単純なコードで、複数のクローズが着信していることに気付きました。それは起こり、両方が閉じるイベント(recv(..、MSG_PEEK)が私にこれを伝えました:))。
epoll fd が作成されます:
epoll_create(1024);
epoll_wait() は次のように実行されます:
epoll_wait(epoll_fd, イベント, 256, 300);
新しい fd は、accept() の後に非ブロッキングとして設定されます:
int flags = fcntl(fd, F_GETFL, 0); err = fcntl(fd, F_SETFL, フラグ | O_NONBLOCK);
新しい fd が epoll に追加されます (クライアントは malloc:ed 構造体ポインターです):
static struct epoll_event ev; ev.events = エポリン | エポロンショット | エポレット; ev.data.ptr = クライアント; err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client->fd, &ev);
そして、fd からデータを受信して処理した後、(もちろん EPOLLONESHOT 以来) 再武装されます。最初は、エッジ トリガーとノンブロッキング io を使用していませんでしたが、テストしたところ、それらを使用してパフォーマンスが大幅に向上しました。ただし、この問題はそれらを追加する前に存在していました。ところで。shutdown(fd, SHUT_RDWR) は、他のスレッドで使用され、サーバーが http エラーなどのために fd を閉じる必要があるときに、epoll を介して適切な閉じるイベントをトリガーします (これが正しい方法であるかどうかは実際にはわかりません)。実行しますが、完全に機能しています)。