Skip to content

Commit

Permalink
Fix TCPServer#accept NPE log
Browse files Browse the repository at this point in the history
Running httpclient test with JRuby master dumps NPE log. Stacktrace says
TCPServer#accept -> IO#select accesses unprotected field 'fd' in
OpenFile. This commit changes IO#select to check if a file is closed or
not first.

I don't have a short reproducer. Following is the step to reproduce.

% git clone https://github.com/nahi/httpclient.git
% cd httpclient
% jruby -Ilib test/test_httpclient.rb -n test_keepalive_disconnected
Loaded suite test/test_httpclient
Started
shed in 0.141 seconds.

1 tests, 21 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

7.09 tests/s, 148.94 assertions/s
Exception in thread "Ruby-0-Thread-4:
/Users/nahi/git-private/jruby/lib/ruby/stdlib/test/unit/testcase.rb:697" java.lang.NullPointerException
    at org.jruby.util.io.OpenFile.channel(OpenFile.java:2250)
    at org.jruby.RubyIO.getChannel(RubyIO.java:402)
    at org.jruby.RubyThread.select(RubyThread.java:1521)
    at org.jruby.ext.socket.RubyTCPServer.accept(RubyTCPServer.java:146)
    at org.jruby.ext.socket.RubyTCPServer$INVOKER$i$0$0$accept.call(RubyTCPServer$INVOKER$i$0$0$accept.gen)
    at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:129)
    at test.test_httpclient.invokeOther7155:accept(test/test_httpclient.rb)
    at test.test_httpclient.RUBY$block$test_keepalive_disconnected$303(test/test_httpclient.rb:1682)
    at org.jruby.runtime.CompiledIRBlockBody.commonYieldPath(CompiledIRBlockBody.java:70)
    at org.jruby.runtime.IRBlockBody.call(IRBlockBody.java:59)
    at org.jruby.runtime.Block.call(Block.java:106)
    at org.jruby.RubyProc.call(RubyProc.java:334)
    at org.jruby.RubyProc.call(RubyProc.java:240)
    at org.jruby.internal.runtime.RubyRunnable.run(RubyRunnable.java:99)
    at java.lang.Thread.run(Thread.java:745)
Hiroshi Nakamura committed Nov 3, 2015
1 parent d6698c2 commit 39f4f2f
Showing 2 changed files with 11 additions and 6 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyIO.java
Original file line number Diff line number Diff line change
@@ -399,7 +399,7 @@ public void close() throws IOException {
*/
public Channel getChannel() {
// FIXME: Do we want to make a faux channel that is backed by IO's buffering? Or turn buffering off?
return openFile.channel();
return getOpenFileChecked().channel();
}

// io_reopen
15 changes: 10 additions & 5 deletions core/src/main/java/org/jruby/util/io/OpenFile.java
Original file line number Diff line number Diff line change
@@ -441,7 +441,7 @@ public boolean waitWritable(ThreadContext context, long timeout) {
try {
if (posix.errno == null) return false;

if (fd == null) throw runtime.newIOError(RubyIO.CLOSED_STREAM_MSG);
checkClosed();

switch (posix.errno) {
case EINTR:
@@ -471,7 +471,7 @@ public boolean waitReadable(ThreadContext context, long timeout) {
try {
if (posix.errno == null) return false;

if (fd == null) throw runtime.newIOError(RubyIO.CLOSED_STREAM_MSG);
checkClosed();

switch (posix.errno) {
case EINTR:
@@ -1391,9 +1391,7 @@ simple read(2) because EINTR does not damage the descriptor.
* MRI: rb_io_wait_readable
*/
boolean waitReadable(ThreadContext context, ChannelFD fd) {
if (fd == null) {
throw context.runtime.newIOError(RubyIO.CLOSED_STREAM_MSG);
}
checkClosed();

boolean locked = lock();
try {
@@ -2248,30 +2246,37 @@ public ChannelFD fd() {
}

public Channel channel() {
assert(fd != null);
return fd.ch;
}

public ReadableByteChannel readChannel() {
assert(fd != null);
return fd.chRead;
}

public WritableByteChannel writeChannel() {
assert(fd != null);
return fd.chWrite;
}

public SeekableByteChannel seekChannel() {
assert(fd != null);
return fd.chSeek;
}

public SelectableChannel selectChannel() {
assert(fd != null);
return fd.chSelect;
}

public FileChannel fileChannel() {
assert(fd != null);
return fd.chFile;
}

public SocketChannel socketChannel() {
assert(fd != null);
return fd.chSock;
}

0 comments on commit 39f4f2f

Please sign in to comment.