3

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 を介して適切な閉じるイベントをトリガーします (これが正しい方法であるかどうかは実際にはわかりません)。実行しますが、完全に機能しています)。

4

5 に答える 5

4

最初の read() が 0 を返すとすぐに、これは接続がピアによって閉じられたことを意味します。この場合、カーネルが EPOLLIN イベントを生成するのはなぜですか? EPOLLIN のみを購読している場合、ソケットの閉鎖を示す方法は他にありません。EPOLLRDHUP を追加できます。これは、0 を返す read() をチェックするのと基本的に同じです。ただし、EPOLLIN をテストする前に、必ずこのフラグをテストしてください。

  if (flag & EPOLLRDHUP) {
     /* Connection was closed. */
     deleteConnectionData(...);
     close(fd); /* Will unregister yourself from epoll. */
     return;
  }

  if (flag & EPOLLIN) {
    readData(...);
  }

  if (flag & EPOLLOUT) {
    writeData(...);
  }

これらのブロックを順序付けた方法は関連性があり、EPOLLRDHUP の戻り値も重要です。これは、deleteConnectionData() が内部構造を破壊した可能性があるためです。閉鎖の場合にも EPOLLIN が設定されるため、いくつかの問題が発生する可能性があります。とにかくデータを生成しないため、EPOLLIN を無視しても安全です。EPOLLRDHUP と一緒に送信されることはないため、EPOLLOUT についても同じです。

于 2011-07-20T13:02:59.053 に答える
1

epoll_wait()は独自のスレッドで実行されており、他の場所で処理されるfdイベントをキューに入れます。...では、サーバーの負荷が高いときに、epollがアプリケーションがクローズイベントを取得しなかったと見なし、新しいイベントをキューに入れるのはなぜですか?

EPOLLONESHOTにバグがないと仮定すると(関連するバグは検索していません)、別のスレッドでepollイベントを処理していて、散発的または高負荷でクラッシュするという事実は、どこかに競合状態があることを意味している可能性があります。応用。

サーバーがクライアント接続をアクティブに閉じるときに、epollイベントが別のスレッドで登録解除される前に、epoll_event.data.ptrが指すオブジェクトの割り当てが途中で解除される可能性があります。

私の最初の試みは、valgrindで実行し、エラーが報告されるかどうかを確認することです。

于 2011-01-18T13:05:24.527 に答える
0

の次のセクションと照らし合わせて自分自身を再確認しますepoll(7)

Q6
ファイル記述子を閉じると、すべての epoll セットから自動的に削除されますか?

o イベントキャッシュを使用している場合...

良い点もあります。

于 2011-01-18T20:06:13.563 に答える
0

EPOLLONESHOT を削除すると、他にいくつかの変更を加えた後、問題が解消されました。残念ながら、何が原因なのか完全にはわかりません。スレッドで EPOLLONESHOT を使用し、fd を epoll キューに手動で再度追加することは、間違いなく問題でした。また、epoll 構造体のデータ ポインタは、遅延後に解放されます。今は完璧に動作します。

于 2011-02-01T15:43:50.037 に答える