I am building a client Ruby library that connects to a server and waits for data, but also allows users to send data by calling a method.
The mechanism I use is to have a class that initializes a socket pair, like so:
def initialize
@pipe_r, @pipe_w = Socket.pair(:UNIX, :STREAM, 0)
end
The method that I allow developers to call to send data to the server looks like this:
def send(data)
@pipe_w.write(data)
@pipe_w.flush
end
Then I have a loop in a separate thread, where I select from a socket
connected to the server and from the @pipe_r
:
def socket_loop
Thread.new do
socket = TCPSocket.new(host, port)
loop do
ready = IO.select([socket, @pipe_r])
if ready[0].include?(@pipe_r)
data_to_send = @pipe_r.read_nonblock(1024)
socket.write(data_to_send)
end
if ready[0].include?(socket)
data_received = socket.read_nonblock(1024)
h2 << data_received
break if socket.nil? || socket.closed? || socket.eof?
end
end
end
end
This works beautifully, but only with a normal TCPSocket
as per the example. I need to use an OpenSSL::SSL::SSLSocket
instead, however as per the IO.select docs:
The best way to use IO.select is invoking it after nonblocking methods such as read_nonblock, write_nonblock, etc.
[...]
Especially, the combination of nonblocking methods and IO.select is preferred for IO like objects such as OpenSSL::SSL::SSLSocket.
According to this, I need to call IO.select
after nonblocking methods, while in my loop I use it before so I can select from 2 different IO objects.
The given example on how to use IO.select
with an SSL socket is:
begin
result = socket.read_nonblock(1024)
rescue IO::WaitReadable
IO.select([socket])
retry
rescue IO::WaitWritable
IO.select(nil, [socket])
retry
end
However this works only if IO.select
is used with a single IO object.
My question is: how can I make my previous example work with an SSL socket, given that I need to select from both the @pipe_r
and the socket
objects?
EDIT: I've tried what @steffen-ullrich suggested, however to no avail. I was able to make my tests pass using the following:
loop do
begin
data_to_send = @pipe_r.read_nonblock(1024)
socket.write(data_to_send)
rescue IO::WaitReadable, IO::WaitWritable
end
begin
data_received = socket.read_nonblock(1024)
h2 << data_received
break if socket.nil? || socket.closed? || socket.eof?
rescue IO::WaitReadable
IO.select([socket, @pipe_r])
rescue IO::WaitWritable
IO.select([@pipe_r], [socket])
end
end
This doesn't look so bad, but any input is welcome.