Skip to content

IO.read_nonblock raises EOFError with concurrent reads #5706

@jonathanswenson

Description

@jonathanswenson

Environment

jruby -v:

jruby 9.1.13.0 (2.3.3) 2017-09-06 8e1c115 Java HotSpot(TM) 64-Bit Server VM 25.181-b13 on 1.8.0_181-b13 +jit [darwin-x86_64]

and

jruby 9.1.17.0 (2.3.3) 2018-04-20 d8b1ff9 Java HotSpot(TM) 64-Bit Server VM 25.181-b13 on 1.8.0_181-b13 +jit [darwin-x86_64]

and

jruby 9.2.7.0 (2.5.3) 2019-04-09 8a269e3 Java HotSpot(TM) 64-Bit Server VM 25.181-b13 on 1.8.0_181-b13 +jit [darwin-x86_64]

JRUBY_OPTS:

-J-Dapple.awt.UIElement=true -J-Djava.awt.headless=true -X+O

java -version:

java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

uname -a

Darwin hades.local 18.5.0 Darwin Kernel Version 18.5.0: Mon Mar 11 20:40:32 PDT 2019; root:xnu-4903.251.3~3/RELEASE_X86_64 x86_64

Expected Behavior

When executing IO.read_nonblock on a socket with no data to be read while another thread is concurrently writing to the same socket, an EOFError can be raised. The expected behavior is for an Errno::EAGAIN or Errno::EWOULDBLOCK to be raised for every read_nonblock Same behavior appears to happen when using TCPSockets.

The socket appears to still be functional after these EOFErrors, as more data can still be read/written to/from socket.

In Ruby (C) this either appears to be threadsafe (or at least more threadsafe).

require 'socket'

s1, s2 = UNIXSocket.pair
ITRS = 2000
failures = {}

t1 = Thread.new do
  ITRS.times do |i|
    begin
      s1.read_nonblock(1)
    rescue EOFError, IOError => eof
      failures[i] = eof
    rescue Errno::EAGAIN, Errno::EWOULDBLOCK
      # ignore
    end
  end
end

ITRS.times { |i| s1.write(".") }
t1.join

# verify that despite all the EOFErrors the socket is not actually EOF
s2.write('.')
puts "read from s1 (after all EOFs): #{s1.read_nonblock(1)}"

s1.close
s2.close

# show some info about failures
puts "#{failures.length} / #{ITRS} read_nonblock(1) failed"
iteration, error = failures.first
if error
  puts error
  puts error.backtrace 
end

output on ruby-2.5.1 (MRI) ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]

$ ruby socket_test.rb
read from s1 (after all EOFs): .
0 / 2000 read_nonblock(1) failed

Actual Behavior

output on jruby-9.1.17.0:

$ ruby socket_test.rb
read from s1 (after all EOFs): .
87 / 2000 read_nonblock(1) failed

End of file reached
org/jruby/RubyIO.java:2855:in `read_nonblock'
socket_test.rb:13:in `block in socket_test.rb'
org/jruby/RubyFixnum.java:305:in `times'
socket_test.rb:11:in `block in socket_test.rb'

stack trace with -Xbacktrace.style=full

End of file reached
java/lang/Thread.java:1559:in `getStackTrace'
org/jruby/runtime/backtrace/TraceType.java:244:in `getBacktraceData'
org/jruby/runtime/backtrace/TraceType.java:47:in `getBacktrace'
org/jruby/RubyException.java:245:in `prepareBacktrace'
org/jruby/exceptions/RaiseException.java:216:in `preRaise'
org/jruby/exceptions/RaiseException.java:183:in `preRaise'
org/jruby/exceptions/RaiseException.java:111:in `<init>'
org/jruby/Ruby.java:4127:in `newRaiseException'
org/jruby/Ruby.java:4078:in `newEOFError'
org/jruby/RubyIO.java:2872:in `nonblockEOF'
org/jruby/RubyIO.java:2959:in `getPartial'
org/jruby/RubyIO.java:2864:in `doReadNonblock'
org/jruby/RubyIO.java:2855:in `read_nonblock'
org/jruby/RubyIO$INVOKER$i$0$2$read_nonblock.gen:-1:in `call'
org/jruby/internal/runtime/methods/JavaMethod.java:796:in `call'
org/jruby/internal/runtime/methods/DynamicMethod.java:202:in `call'
org/jruby/runtime/callsite/CachingCallSite.java:153:in `call'
socket_test.rb:13:in `invokeOther1:read_nonblock'
socket_test.rb:13:in `block in socket_test.rb'
org/jruby/runtime/CompiledIRBlockBody.java:156:in `yieldDirect'
org/jruby/runtime/BlockBody.java:114:in `yield'
org/jruby/runtime/Block.java:165:in `yield'
org/jruby/RubyFixnum.java:305:in `times'
org/jruby/RubyFixnum$INVOKER$i$0$0$times.gen:-1:in `call'
org/jruby/runtime/callsite/CachingCallSite.java:308:in `cacheAndCall'
org/jruby/runtime/callsite/CachingCallSite.java:137:in `call'
org/jruby/runtime/callsite/CachingCallSite.java:142:in `callIter'
socket_test.rb:11:in `invokeOther4:times'
socket_test.rb:11:in `block in socket_test.rb'
org/jruby/runtime/CompiledIRBlockBody.java:145:in `callDirect'
org/jruby/runtime/IRBlockBody.java:71:in `call'
org/jruby/runtime/Block.java:124:in `call'
org/jruby/RubyProc.java:289:in `call'
org/jruby/RubyProc.java:246:in `call'
org/jruby/internal/runtime/RubyRunnable.java:104:in `run'
java/lang/Thread.java:748:in `run'

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions