Ok...after baffling myself why this isn't working and why I can't fix it...I ran it in MRI. It doesn't work there either.
I'm not sure if it was an oddity of the original script or a different patchlevel, but the problem here is even further away from the actual IO stream. It's in the implementation of read buffering in openssl/buffering.rb.
SSLSocket only implements a low-level sysread, and has its own buffers behind the scenes to make it look like any other IO stream. There is certainly still a hack needed to allow select operations to see that there's data there when it has been buffered and possibly decoded already, but that's not where the problem seems to lie in this script.
I added logging to the logic below sysread, where it tries to read N bytes out of the internal buffers. What I found was that the very first read(1) attempts to read 16384 bytes, effectively draining the stream right away.
This is due to openssl/buffering.rb in its implementation of read(), which attempts to fill a Ruby-land buffer.
So at least in JRuby, there's four levels of buffering at play: the buffer in the native file descriptor, at the kernel level (this is where select would see data available); the buffers for raw data and decrypted data in SSLSocket; and the buffer in openssl/buffering.rb.
I added a test based on my script from above. Ruby 1.9 and 2.0 freeze while running it just like JRuby with no fix in place. And they both freeze after reading the first byte off the wire.
I think the original case might have been too simple to actually exercise these buffers completely. The first write very likely yielded to the reading thread immediately, so it only saw one byte. This is how MRI's scheduler works. So the second byte was selectable and readable because it was not there for the first read to drain. With my modified script, where all the bytes are written at once...the first read drains the stream and the select hangs because there does not appear to be any data on the wire.
I'm going to revert my changes and hope we can figure out the real problem here, because the given test case is actually a red herring and all versions of Ruby have the same problem with buffered SSLSocket reads combined with IO select.