Closed13

tonic/tokioに興味を持ったのでコアっぽいmioについて調べてみる

mars2nicomars2nico

ここらへんの私の理解があいまい

  • 非同期とノンブロッキングってどう違うの
  • ノンブロッキングの文脈で登場するselect, epollを非同期で使っているのかどうか
mars2nicomars2nico
$ git clone 
$ cargo r --bin echo

※一応Windowsでも動くようにしたかったのでstd::netを使用するように書き換えた

まず、動作確認。

$ echo hello! | nc localhost 8080
hello!
^C

straceする。ブロッキングなのでepollは使われていない。

strace ./target/debug/echo
execve("./target/debug/echo", ..
..
accept4(3, {sa_family=AF_INET, sin_port=htons(58406), sin_addr=inet_addr("127.0.0.1")}, [128 => 16], SOCK_CLOEXEC) = 4
recvfrom(4, "hello!\n", 1024, 0, NULL, NULL) = 7
sendto(4, "hello!\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024, MSG_NOSIGNAL, NULL, 0) = 1024
recvfrom(4, "", 1024, 0, NULL, NULL)    = 0
close(4)                                = 0
mars2nicomars2nico

mio::net::TcpListener, mio::Poll等を使ってmio版echoサーバーを作成。

straceしてブロッキング版と比較する。
期待通り、epoll_ctl, epoll_waitが呼ばれていることが分かる。
また、ノンブロッキングでacceptされていることが分かる。

$ strace ./target/debug/mio_echo
execve("./target/debug/mio_echo", ..
..
epoll_wait(3, [{events=EPOLLIN, data={u32=32, u64=32}}], 32, -1) = 1
accept4(4, {sa_family=AF_INET, sin_port=htons(40738), sin_addr=inet_addr("127.0.0.1")}, [128 => 16], SOCK_CLOEXEC|SOCK_NONBLOCK) = 5
epoll_ctl(3, EPOLL_CTL_ADD, 5, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=2, u64=2}}) = 0
epoll_wait(3, [{events=EPOLLIN|EPOLLOUT, data={u32=2, u64=2}}], 32, -1) = 1
recvfrom(5, "hello!\n", 1024, 0, NULL, NULL) = 7
sendto(5, "hello!\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1024, MSG_NOSIGNAL, NULL, 0) = 1024
epoll_wait(3, [{events=EPOLLIN|EPOLLOUT|EPOLLRDHUP, data={u32=2, u64=2}}], 32, -1) = 1
recvfrom(5, "", 1024, 0, NULL, NULL)    = 0
epoll_ctl(3, EPOLL_CTL_DEL, 5, NULL)    = 0
close(5)                                = 0
..
mars2nicomars2nico

作成したmio版echoサーバーがシグナルSIGWINCHのあと異常終了してしまう

..
epoll_wait(3, 0x55af5196fba0, 32, -1)   = -1 EINTR (Interrupted system call)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
..
+++ exited with 1 +++

本題からそれそうなのでいったん放っておく

mars2nicomars2nico

tokio::net::TcpListener, #[tokio::main]等を使ってasync/await版echoサーバーを作成中、詰まる。

#[tokio::main]
async fn main()
..
    let listner = TcpListener::bind("127.0.0.1:8080".parse()?)?;
..

修正したソースはこれ。

#[tokio::main]
async fn main()
..
    let listner = TcpListener::bind("127.0.0.1:8080".parse()?).await?;
..

unwrapできないと思ったら、awaitを挟むらしい

.NETのasync/await風に解釈するとここのbindはTaskを返すのでawaitしないとlistnerはTaskになる(この理解だとまた詰まりそう)

mars2nicomars2nico

tokio::net::TcpListener, #[tokio::main]等を使ってasync/await版echoサーバーを作成。

straceしてmio版と比較。
見慣れない futex や<... {name} resumed>の出力。
重要そうでないものを省略。

期待に反してスレッドが3つ以上立っている。

$ strace -tf target/debug/aa_echo
20:08:51 execve("target/debug/aa_echo", ..
..
[pid  5501] 20:08:51 futex(0x7f5d6a35f5e8, FUTEX_WAIT_PRIVATE, 1, NULL <unfinished ...>
[pid  5496] 20:08:58 <... epoll_wait resumed>[{events=EPOLLIN, data={u32=0, u64=0}}], 1024, -1) = 1
..
[pid  5493] 20:08:58 <... futex resumed>) = 0
[pid  5496] 20:08:58 <... futex resumed>) = 1
..
[pid  5493] 20:08:58 <... accept4 resumed>{sa_family=AF_INET, sin_port=htons(50966), sin_addr=inet_addr("127.0.0.1")}, [128 => 16], SOCK_CLOEXEC|SOCK_NONBLOCK) = 10
[pid  5493] 20:08:58 epoll_ctl(5, EPOLL_CTL_ADD, 10, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=1, u64=1}}) = 0
[pid  5496] 20:08:58 <... epoll_wait resumed>[{events=EPOLLIN|EPOLLOUT, data={u32=1, u64=1}}], 1024, -1) = 1
[pid  5493] 20:08:58 futex(0x7f5d6a35f5e8, FUTEX_WAKE_PRIVATE, 1) = 1
..
[pid  5501] 20:08:58 <... futex resumed>) = 0
[pid  5493] 20:08:58 <... accept4 resumed>0x7ffd46f3a9b8, [128], SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)
..
[pid  5501] 20:08:58 <... futex resumed>) = 1
[pid  5500] 20:08:58 <... futex resumed>) = 0
..
[pid  5501] 20:08:58 <... recvfrom resumed>"hello!\n", 1024, 0, NULL, NULL) = 7
[pid  5501] 20:08:58 sendto(10, "hello!\n", 7, MSG_NOSIGNAL, NULL, 0) = 7
..
[pid  5496] 20:09:01 <... epoll_wait resumed>[{events=EPOLLIN|EPOLLOUT|EPOLLRDHUP, data={u32=1, u64=1}}], 1024, -1) = 1
[pid  5496] 20:09:01 recvfrom(10, "", 1024, 0, NULL, NULL) = 0
[pid  5496] 20:09:01 epoll_ctl(5, EPOLL_CTL_DEL, 10, NULL) = 0
[pid  5496] 20:09:01 close(10)          = 0
mars2nicomars2nico

少し詰まった感があったが、マクロ#[tokio::main]のドキュメントでスレッド数を指定できることに気づく。

さきほどunfinishedは勝手に重要そうじゃないと思ったのだが、
どうやらunfinishedとresumedは対応関係にあるようなのでそのまま残しておく。
futexのみ一旦トレースから除外する。

期待通り、スレッド数は2つだけになった。
さっきもぱっと見で気づいていたが、非同期でもepollを使っていることが分かった。
最初の目的は達成した。

strace -tf -e 'trace=!futex' target/debug/aa_echo
21:12:56 execve("target/debug/aa_echo", ..
..
[pid  8095] 21:12:56 epoll_ctl(5, EPOLL_CTL_ADD, 9, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=0, u64=0}} <unfinished ...>
..
[pid  8095] 21:12:56 <... epoll_ctl resumed>) = 0
..
[pid  8096] 21:12:56 epoll_wait(3, [{events=EPOLLIN, data={u32=0, u64=0}}], 1024, -1) = 1
[pid  8095] 21:13:08 accept4(9,  <unfinished ...>
[pid  8096] 21:13:08 epoll_wait(3,  <unfinished ...>
[pid  8095] 21:13:08 <... accept4 resumed>{sa_family=AF_INET, sin_port=htons(38060), sin_addr=inet_addr("127.0.0.1")}, [128 => 16], SOCK_CLOEXEC|SOCK_NONBLOCK) = 10
[pid  8095] 21:13:08 epoll_ctl(5, EPOLL_CTL_ADD, 10, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=1, u64=1}}) = 0
[pid  8096] 21:13:08 <... epoll_wait resumed>[{events=EPOLLIN|EPOLLOUT, data={u32=1, u64=1}}], 1024, -1) = 1
[pid  8095] 21:13:08 write(4, "\1\0\0\0\0\0\0\0", 8) = 8
[pid  8096] 21:13:08 recvfrom(10,  <unfinished ...>
[pid  8095] 21:13:08 accept4(9,  <unfinished ...>
[pid  8096] 21:13:08 <... recvfrom resumed>"hello!\n", 1024, 0, NULL, NULL) = 7
[pid  8095] 21:13:08 <... accept4 resumed>0x7ffeb3d53aa8, [128], SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)
[pid  8096] 21:13:08 sendto(10, "hello!\n", 7, MSG_NOSIGNAL, NULL, 0) = 7
[pid  8096] 21:13:08 epoll_wait(3, [{events=EPOLLIN, data={u32=2147483648, u64=2147483648}}], 1024, -1) = 1
[pid  8096] 21:13:08 epoll_wait(3, [{events=EPOLLIN|EPOLLOUT|EPOLLRDHUP, data={u32=1, u64=1}}], 1024, -1) = 1
[pid  8096] 21:13:11 recvfrom(10, "", 1024, 0, NULL, NULL) = 0
[pid  8096] 21:13:11 epoll_ctl(5, EPOLL_CTL_DEL, 10, NULL) = 0
[pid  8096] 21:13:11 close(10)          = 0
..
mars2nicomars2nico

ここらへんがまだあいまい。

  • futexの周り
  • sendtoの後にepoll_waitが2回呼ばれている理由
  • tokioによってmioを直接叩くことから何が解放されたのか

たぶん知らなくてもいいことだろうけど

  • epoll(Linuxカーネル 2.5.44)で何が変わったのか

も少し気になる

次のタスク:
dtraceが一般でも使えるようになったらWindowsでも調べてみる

このスクラップは2023/01/09にクローズされました