macOS と Linux の UNIX Domain Socket の SO_SNDBUF, SO_RCVBUF について調べていた。
経緯
会社で @kurotaky のトラブルシューティングの相談を受けた際の覚書。
下記のように UNIX Domain Socket を挟んで IPC しているコードがあり、macOS と Linux で挙動が違っているのを調べていた。Linux では Go Ethereum が送ってくるデータを全部読み出せるが、macOS だと一部 ( 8192 バイト 注1 ) しか読み出せないとのことだった。
Ruby のコード <----- UNIX Domain Socket -----> Go Ethereum
色々調べてみたところ、macOS / Linux で UNIX Domain Socket のデフォルトの SO_SNDBUF, SO_RCVBUF (注2) が違うことで、Ruby 側で 1回の recvmsg(2) で取り出せるデータのサイズが異なっているのが問題だったようだ。
UNIX Domain Socket の SO_SNDBUF / SO_RCVBUF のサイズをとる
socket の SO_SNDBUF をとるには getsockopt(2) を呼び出す
Ruby でかくとこんなんかな?
#!/usr/bin/env ruby require 'socket' sender, receiver = UNIXSocket.pair # あるいは SO_RCVBUF opt = receiver.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF) p opt
SO_SNDBUF のサイズ: macOS の場合
手元の macOS のバージョンは以下の通り
$ uname -a Darwin PM-* 21.4.0 Darwin Kernel Version 21.4.0: Mon Feb 21 20:35:58 PST 2022; root:xnu-8020.101.4~2/RELEASE_ARM64_T6000 arm64
検証コードを実行すると 8192 だった
$ ./test.rb #<Socket::Option: UNIX SOCKET SNDBUF 8192>
macOS では、sysctl net.local.stream.sendspace で SO_SNDBUF のデフォルトのサイズを変更することができた
$ sudo sysctl -w net.local.stream.sendspace=16384 net.local.stream.sendspace: 8192 -> 16384
sysctl を変更後は 16384 になる
$ ruby test.rb #<Socket::Option: UNIX SOCKET SNDBUF 16384>
Linux の場合
multipass で起動した Linux 5.4 系で試す
$ uname -a Linux primary 5.4.0-105-generic #119-Ubuntu SMP Mon Mar 7 18:50:13 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux
Linux では 212992 だった
$ ruby test.rb #<Socket::Option: UNIX SOCKET SNDBUF 212992>
Linux では sysctl net.core.wmem_default でデフォルトのサイズを変えることができる
$ sysctl net.core.wmem_default net.core.wmem_default = 212992 $ sudo sysctl -w net.core.wmem_default=8192
変更すると 8192 になった
$ ruby test.rb #<Socket::Option: UNIX SOCKET SNDBUF 8192>
UNIX Domain Socket と SO_SNDBUF と送信でのブロック
UNIX Domain Socket で、SO_SNDBUF を超えるサイズのデータを送ろうとするとブロックする (注意: ソケットがブロッキングモードの場合) 。読み手がバッファのデータを読み出さない限りブロックし続ける
#!/usr/bin/env ruby require 'socket' sender, receiver = UNIXSocket.pair warn "try to sendmsg morethan 8192 bytes" sender.sendmsg "@" * 8192 # ここでブロックする warn "try to sendmsg 1 bytes" sender.sendmsg "@" * 1 warn "done sendmsg"
検証コードを実行した結果
$ ruby ~/hoge.rb try to sendmsg morethan 8192 bytes
感想
しばらくシステムプログラミングのレイヤから遠ざかっていたので、リハリビになった。ブロックする挙動のこともすっかり忘れていた
macOS のカーネルで net.local.stream.sendspace
を参照しているあたりのコードを調べたかったが、どこ調べたらいいかわからん。↓ ではないのかなぁ
Linux だと net/unix/af_unix.c を読むといい
参照
同僚の 🕶 が書いたエントリで net.core.wmem_default, net.core.rmem_default の存在を思い出します
net.ipv4.tcp_rmem と net.ipv4.tcp_wmem については以前調べた拙著のエントリがあります
追記
- 注1 8192 バイトであることはエントリを公開後 追記した
- 注2 UNIX Domain Socket では
SO_SNDBUF ソケットオプションは UNIX ドメインソケットで効果を持つが、 SO_RCVBUF は効果がない。
と man に書いてある- https://linuxjm.osdn.jp/html/LDP_man-pages/man7/unix.7.html
- id:ryuichi1208 🕶 にエントリを公開後教えてもらった。感謝!
- ブロックを検証するコードが間違っていたので修正した