まだまだ消費増税に対抗するため、無理やりネタを紡ぎ出す。
named pipe、「名前付きパイプ」の簡単な使い方を残しておく。
プロセス間通信
プロセスの間でなにかしらのデータのやり取りをすることをプロセス間通信(Inter Process Communication)と呼ぶ。たとえばLinuxでは以下のような方法がある。
- シグナル
- パイプ、FIFO
- SysV IPC(メッセージキュー、セマフォ、共有メモリ)
- POSIX IPC(同上)
- UNIXドメインソケット
- インターネットドメインソケット(いわゆるTCP/UDPなどのソケット)
この分類は「Linuxプログラミングインターフェース」を参考にした。
IPCで最も手軽なのはパイプの作成で、fork元でpipe()などを用いて読み出し専用/書き込み専用の1組のパイプを作り、fork先でもそのfile descriptorを継承して使う。お互いにread/writeという基本的なシステムコールのみでデータをやり取りできるメリットがある。
mruby(CRubyも同様)の例を昔書いた。
一方でパイプは、fdを共有できるような、例えば親子関係があるプロセス同士でないと利用できない。そこでファイルシステム上に作成できる名前付きパイプを使う。
基本的な使い方
MacまたはLinux前提の話とする(他のUNIXでも大丈夫だと思う)。
mkfifo
というコマンドで名前付きパイプを作成できる。
$ mkfifo /tmp/foo.fifo $ file /tmp/foo.fifo /tmp/foo.fifo: fifo (named pipe)
このファイルに書き込みと、ブロックをする。
$ echo hello pipe > /tmp/foo.fifo
別のターミナルでこのファイルを cat
して読み出す。
$ cat /tmp/foo.fifo hello pipe
すると、元のターミナルのブロックも解除されている。
名前付きパイプの基本的な使い方として:
- 書き込むと、どこかで読み出されない限り書き込みが完了しない(ブロックする)
- 読み出そうとすると、読み出し可能な状態になるまでブロックする
ブロックする性質を利用して、あるプロセスが何かしらの処理が完了するのを別のプロセスから待つことができる。簡単には、何かしらの処理が完了したらパイプに何かを書き込み、それを事前に別のプロセスでreadしておけば待ち受けることができる。
ノンブロッキングの場合はまた扱いが変わる。次はRubyのスクリプトでやってみる。
ノンブロッキングモードでの書き込みの場合、パイプが書き込み可能になる(片方で誰かが読み込んでいる)状態でないと即座にエラーになる。
f1 = File.open("/tmp/fifo.2.fifo", File::WRONLY | File::NONBLOCK) Traceback (most recent call last): 4: from /Users/udzura/.rbenv/versions/2.5.5/bin/irb:11:in `<main>' 3: from (irb):15 2: from (irb):15:in `open' 1: from (irb):15:in `initialize' Errno::ENXIO (Device not configured @ rb_sysopen - /tmp/fifo.2.fifo) ## もう片方で cat /tmp/fifo.2.fifo f1 = File.open("/tmp/fifo.2.fifo", File::WRONLY | File::NONBLOCK) #=> #<File:/tmp/fifo.2.fifo> f1.write_nonblock("Test\n") #=> 5 # cat したプロセスで即座に読み込める f1.write("Test\n") f1.write("Test\n") f1.flush # あるいは、このタイミングで書き込んで、もう一方で読み込める
ノンブロッキングモードでの読み出しは、ブロックはせず、fdを作成する。そこから読み出そうとすると、読み出し可能になるまで即座にエラーになる。
f2 = File.open("/tmp/fifo.2.fifo", File::RDONLY | File::NONBLOCK) f2.read_nonblock(2048) Traceback (most recent call last): 4: from /Users/udzura/.rbenv/versions/2.5.5/bin/irb:11:in `<main>' 3: from (irb):2 2: from <internal:prelude>:74:in `read_nonblock' 1: from <internal:prelude>:74:in `__read_nonblock' EOFError (end of file reached) ## もう片方で echo test > /tmp/fifo.2.fifo f2.read_nonblock(2048) #=> "test\n" f1 = File.open("/tmp/fifo.2.fifo", File::WRONLY | File::NONBLOCK) => #<File:/tmp/fifo.2.fifo> f2.read_nonblock(2048) Traceback (most recent call last): 4: from /Users/udzura/.rbenv/versions/2.5.5/bin/irb:11:in `<main>' 3: from (irb):9 2: from <internal:prelude>:74:in `read_nonblock' 1: from <internal:prelude>:74:in `__read_nonblock' # パイプが「つながっている」状態の時はエラーが変わる IO::EAGAINWaitReadable (Resource temporarily unavailable - read would block)
コンテナの間で使う
コンテナの間でボリュームを共有すれば、(実態はただの bind mount であろうので)同じホストであれば名前付きパイプを通して通信ができる。
$ docker run -v/tmp/fifos:/tmp/fifos -ti ruby:2.6 bash root@25860f875ba7:/# irb irb(main):001:0> loop do irb(main):002:1* data = File.readlines("/tmp/fifos/myfifo.fifo") irb(main):003:1> unless data.empty? irb(main):004:2> data.each {|ln| puts "Got=> #{ln}" } irb(main):005:2> end irb(main):006:1> end # --- もう一方のコンテナ $ docker run -v/tmp/fifos:/tmp/fifos -ti ruby:2.6 bash root@ffb25abec6e0:/# cat > /tmp/fifos/myfifo.fifo Foo Bar Buz!! # --- 元のコンテナで Got=> Foo Got=> Bar Got=> Buz!!
例えば Kubernetes のばあい同じ Pod のコンテナは同じホストにできるそうなので、高速なコンテナ間通信のいち手段に使えるかもしれない。
それを利用した pikebubbles というコンテナツールを作っていたりする。
特にオチもなく終わる。皆さんもたまには名前付きパイプのことを思い出しましょう。
急いで書いたので細部でおかしなところがあれば是非ご指摘を...。