🛞

HTTP Serverを実行するための簡素な非同期ランタイムを作った

2024/02/18に公開

TL;DR

async/awaitの裏で何が起こっているのか理解するためにランタイムと簡素なHTTP サーバーを作った。
https://gitlab.com/Toru3/http_server
内容としては以下の2番煎じに近いがいくつか違う点がある。
https://mmi.hatenablog.com/entry/2019/09/29/203156

動機

最近詳解UNIXプログラミング 第3版を読む機会があり、その中でselectpollというシステムコールを知った。そこで、理解を深めるために(C++で)selectを使った簡素なHTTPサーバーを作ってみた。ところで筆者はActixで簡単なHTTPサーバーを建てたことがある。そこでRustの非同期ランタイムはこのシステムコールを使っているのだろうかと興味が湧いた。そのため、(Actixで)簡単なHTTPサーバーを立て、straceで呼ばれているシステムコールを観察した。すると、前述のselectやpollではなくepollが使われていることが分かった。また、非同期ランタイムについて調べて以下の記事を見つけて、意外と簡単に作れそうな気がしてきたため自作してみた。
https://qiita.com/legokichi/items/1beb3dce317ef45a927b
https://keens.github.io/blog/2019/07/07/rustnofuturetosonorunnerwotsukuttemita/
https://mmi.hatenablog.com/entry/2019/09/29/203156

上記の記事との相違点

肝となるReactorやExecutorの設計などはRustのasync/awaitを使ったecho serverの実装と同じであるが以下の点が違う。

  • futuresクレートは使用していない
  • システムコールを直接呼ばずにstd::netnixを使っている
    • このため直接unsafeを使っているのはWakerを作成するのに最低限必要なものだけになっているはず
  • エラー型はthiserrorを使用している
  • シグナル処理を追加している
  • echoサーバーではなくHTTPサーバーである
  • 準備が出来たファイル記述子に対し、epoll_ctl(EPOLL_CTL_DEL, ..)を呼んでいない。
    • このためにepollに登録しているファイル記述子とイベントをユーザランドで保持している
    • これによりepoll_ctlを呼ぶ回数が少なくなっている

動作確認

1GbpsのLANに繋がっているLinuxマシン2台を使った。1台は今回作成したサーバーを起動しておき、もう1台からcurlを使ってアクセスする。ちなみにcurlに--parallel-maxというオプションがあるのだが、これは300より大きい値を指定しても50になってしまう。負荷試験として1000コネクション程度並列でリクエストを投げてみたが、問題なく動いているようだった。また、ファイルサイズが数百KB程度あれば950Mbps程度出せることが分かった。さらに、950Mbps程度出していてもCPU使用率は1コアの30%程度しか使われないことも分かった。このため、epollがきちんと機能していると判断した。

感想

async/awaitは魔法ではないことがよく分かった。

Discussion