2023-8-15

なぜEdgeTriggerモードのときはNonBlockingIOを使うべきか

Nginx

自作サーバー

自作サーバーでIOをEPOLLで多重化しようとしていて、 LevelTriggerとEdgeTriggerどちらを使うべきか悩んでいる。 そもそもそれぞれのメリット、デメリットもよく理解できていない状態だが、 それ以上にepoll(7)に記載されている。 「EdgeTriggerを使うならNonBlockingなファイルディスクリプタを使いなさい」という説明がなかなか理解できなかった。

An application that employs the EPOLLET flag should use nonblocking file descriptors to avoid having a blocking read or write starve a task that is handling multiple file descriptors. The suggested way to use epoll as an edge-triggered (EPOLLET) interface is as follows: (1) with nonblocking file descriptors; and (2) by waiting for an event only after read(2) or write(2) return EAGAIN.

EdgeTriggerで動作させる場合「nonblockingなファイルディスクリプタ(以下fd)を使って、 かつread(2)かwrite(2)がEAGAINだった場合にのみwaitしなさい」という内容だ。 後者のEAGAINだった場合にのみ〜という内容は比較的自明な気がしていて、 read可能なのに次のEdgeの立ち上がりを待ってしまうと。 未来でEdgeが立ち上がらなかった場合、 最初に入っていたデータが放置され続けてしまう。 特に相手が返答を待っている状態だった場合に通信がハングしてしまう。

一方で前者のnonblockingなfdを使うべき、という主張の部分が知識不足で理解できなかったので簡単に調査した。

何となくの理解

StackOverflowのこの記事などを参考にした。 おそらく以下のような事象が発生するからだと考えられる。

LevelTriggerでepollを使用し、かつブロッキングでreadするケースを考える。

  • ファイルディスクリプタ(以下fd)にデータの一部が書き込まれ、 epoll_waitが検知する。
  • readを開始する。
  • 相手側が何かしらの理由でEOFを送る前にファイルの送信をやめた場合、 無限にブロックされる。

まとめるとEdgeTriggerを使う場合には以下2点に気をつける必要がある。(1に関してはレベルトリガーも同じな気がする)

  1. 場合によっては無限にブロックされる可能性があるため、 non-blockingなIOを使うべき。
  2. 一度Edgeが立ったIOを無視した場合に、 通信がハングしてしまう可能性がある。

参考