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.
I'm not familiar with ruby itself, but with the problems of using select with SSL based sockets. SSL sockets behave differently to TCP sockets since SSL data are transferred in frames and not as a data stream, but nevertheless stream semantics are applied to the socket interface.
Let's explain this with an example, first using a simple TCP connection:
With SSL this is different:
How to deal with this difference:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With