rubyにおけるソケットのaccept_nonblockの競合

Rubyで1つのサーバーソケットを2つのプロセスから同時にaccept_nonblockするとどうなるか。サーバーのgraceful restartでよくありそうなシチュエーションを試してみる。

実験コード:

require 'io/wait'
require 'socket'

s = TCPServer.new(0)
p [ :listen, s ]

fork{
  begin
    p [ $$, :wait, s.wait_readable(3600) ]
    p [ $$, :accept, s.accept_nonblock ]
  rescue
    p [ $$, $! ]
  end
}

fork{
  begin
    p [ $$, :wait, s.wait_readable(3600) ]
    p [ $$, :accept, s.accept_nonblock ]
  rescue
    p [ $$, $! ]
  end
}

sleep 1

fork{
  TCPSocket.new('localhost', s.local_address.ip_port)
}

Process.waitall

結果:

$ ruby sock_accept_nonblock_dup.rb
[:listen, #<TCPServer:fd 5, AF_INET, 0.0.0.0, 45821>]
[13290, :wait, #<TCPServer:fd 5, AF_INET, 0.0.0.0, 45821>]
[13291, :wait, #<TCPServer:fd 5, AF_INET, 0.0.0.0, 45821>]
[13291, :accept, #<TCPSocket:fd 6, AF_INET, 127.0.0.1, 45821>]
[13290, #<IO::EAGAINWaitReadable: Resource temporarily unavailable - accept(2) would block>]

やはり片方が成功しもう片方はリトライしなければならないようだ。