OpenFile#channel() reads the non-volatile field fd twice: checkClosed()
first, then return fd.ch. Another thread closing the IO sets fd = null
(OpenFile.java:953) between the two reads, so the caller gets a Java NPE
rather than the IOError the check was there to raise.
Java::JavaLang::NullPointerException:
Cannot read field "ch" because "this.fd" is null
org/jruby/util/io/OpenFile.java:2584 in channel
org/jruby/RubyIO.java:481 in getChannel
org/jruby/ext/socket/RubyTCPServer.java:292 in getServerSocketChannel
org/jruby/ext/socket/RubyTCPServer.java:159 in accept
Reproduced on jruby-10.0 at 6ab9bcc, JDK 25: the suite's own
test_accept_on_closed_server_raises_ioerror (test/jruby/test_io.rb) fails
about 1 run in 50, and 8 in 10 with a Thread.yield() inserted between the
check and the dereference. It shows up as an intermittent CI failure on
rake test:jruby:jit.
The window appeared in b84d783 (the fix for #9324), which replaced
assert(fd != null) with checkClosed(); the assert had the same two-read
shape but was disabled in normal runs.
ready() dereferences fd with no check at all, and setBlocking() has the
same pattern.
Fix: read the field once. PR to follow.
OpenFile#channel()reads the non-volatile fieldfdtwice:checkClosed()first, then
return fd.ch. Another thread closing the IO setsfd = null(OpenFile.java:953) between the two reads, so the caller gets a Java NPE
rather than the IOError the check was there to raise.
Reproduced on jruby-10.0 at 6ab9bcc, JDK 25: the suite's own
test_accept_on_closed_server_raises_ioerror(test/jruby/test_io.rb) failsabout 1 run in 50, and 8 in 10 with a
Thread.yield()inserted between thecheck and the dereference. It shows up as an intermittent CI failure on
rake test:jruby:jit.The window appeared in b84d783 (the fix for #9324), which replaced
assert(fd != null)withcheckClosed(); the assert had the same two-readshape but was disabled in normal runs.
ready()dereferencesfdwith no check at all, andsetBlocking()has thesame pattern.
Fix: read the field once. PR to follow.