SSLSocket in non-blocking mode

I’ve had a little trouble getting SSLSocket to behave as I want it to.
It’s #pending method returns 0 until I call #sysread(1)… which throws
EOFError if there is nothing to read, or 1 byte after some bytes have
become available.

I suspect that the implementation does not fill its internal buffer with
raw bytes to decrypt to its output buffer until #sysread is called on an
empty output buffer. Given that the SSLSocket class almost certainly
would not be implementing its own thread to do this automatically, that
seems reasonable.

But my solution seems a bit “hackish”, and I would appreciate if someone
knows a better way to do this?

(Maybe IO:select on the tcp socket to see if it has bytes ready, then
call #sysread)

Following is a simplified code snippet that shows essentially what I’m

class SslSocketServer
def initialize(port, hostname, cert_fname, key_fname)
@socket_server =, SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(port, hostname)
@ssl_context =
cert_file =
key_file =
@ssl_context.cert =
@ssl_context.key =

def accept
ssl = nil
sock, addr = @socket_server.accept
sslsock =, @ssl_context)
sslsock.sync_close = true
ssl =, sslsock, addr)
rescue SSLError => ex
raise ex
rescue IO::WaitReadable
rescue Errno::EINTR => e
STDERR.puts("#{e.class}: #{e.to_s}")

class SslConnection
def initialize(sock, sslsock, addr)
@sock = sock
@sslsock = sslsock
@addr = addr

def recvfrom_nonblock(size)
available = @sslsock.pending
if available == 0
byte = @sslsock.sysread(1)
[byte, @addr]
rescue EOFError
raise SslNotEnoughPending, “EOFError”
size = available if available < size
[@sslsock.sysread(size), @addr]

def close

def write(bytes)

Then I read from the SslConnection as if it were a socket returned from
Socket#accept, using the rcvfrom_nonblock and write methods only.

I’ve done some more work on this since posting, and tested out an idea I
hinted at in my previous post:
I check the underlying tcpsocket (@sock in my code) using
(Actually I call select passing it an array of the currently connected
tcp sockets, and if there are any bytes waiting, then if I call sysread
on the SSLSocket, it will fill its input buffer from the tcpsocket,
decrypt them, and return them.
So I guess I answered my own question.

Also, it seems logical to me now that #pending will actually only tell
me how many bytes are remaining in the output buffer of the SLSocket,
because the SSLSocket won’t read from the tcpsocket unless its buffer is
-empty-. I.e. when #pending is returning zero :slight_smile:
So I have to be optimistic, read one byte (only when I know, from the
call to, that there are bytes ready for it to process)…
which will trigger it to read from the TCPSocket, and decrypt it, and
fill its output buffer, and then #pending will be non zero.

I’m making assumptions here, and would be grateful if someone can
provide an authoritative answer.

Are you not aware OpenSSL::SSL::SSLSocket has a #read_nonblock method?

Not according to this documentation:

… but according to irb, yes! Thanks

irb(main):015:0> sslsock.methods
=> [:io, :context, :hostname, :hostname=, :sync_close, :sync_close=,
:to_io, :connect, :connect_nonblock, :accept, :accept_nonblock,
:sysread, :syswrite, :sysclose, :cert, :peer_cert, :peer_cert_chain,
:cipher, :state, :pending, :session_reused?, :session=, :verify_result,
:client_ca, :post_connection_check, :session, :addr, :peeraddr,
:setsockopt, :getsockopt, :fcntl, :closed?, :do_not_reverse_lookup=,
:sync, :sync=, :read, :readpartial, :read_nonblock, :gets, :each,
:each_line, :readlines, :readline, :getc, :each_byte, :readchar,
:ungetc, :eof?, :eof, :write, :write_nonblock, :<<, :puts, :print,
:printf, :flush, :close, :to_a, :entries, :sort, :sort_by, :grep,
:count, :find, :detect, :find_index, :find_all, :select, :reject,
:collect, :map, :flat_map, :collect_concat, :inject, :reduce,
:partition, :group_by, :first, :all?, :any?, :one?, :none?, :min, :max,
:minmax, :min_by, :max_by, :minmax_by, :member?, :include?,
:each_with_index, :reverse_each, :each_entry, :each_slice, :each_cons,
:each_with_object, :zip, :take, :take_while, :drop, :drop_while, :cycle,
:chunk, :slice_before, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>,
:class, :singleton_class, :clone, :dup, :initialize_dup,
:initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?,
:trust, :freeze, :frozen?, :to_s, :inspect, :methods,
:singleton_methods, :protected_methods, :private_methods,
:public_methods, :instance_variables, :instance_variable_get,
:instance_variable_set, :instance_variable_defined?, :instance_of?,
:kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?,
:respond_to_missing?, :extend, :display, :method, :public_method,
:define_singleton_method, :object_id, :to_enum, :enum_for, :==, :equal?,
:!, :!=, :instance_eval, :instance_exec, :send, :id]

Weird, they seem to be “documented” here instead. Seems bad: