Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions core/src/main/java/org/jruby/RubyIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -2246,9 +2246,13 @@ public RubyBoolean tty_p(ThreadContext context) {

fptr.lock();
try {
if (posix.isNative() && fptr.fd().realFileno != -1) {
// read fd once: a close in another thread nulls it between the check above and here
ChannelFD fd = fptr.fd();
if (fd == null) throw runtime.newIOError(CLOSED_STREAM_MSG);

if (posix.isNative() && fd.realFileno != -1) {
// IO is native and we can call isatty
return Convert.asBoolean(context, posix.libc().isatty(fptr.getFileno()) == 1);
return Convert.asBoolean(context, posix.libc().isatty(fd.bestFileno(true)) == 1);
} else if (fptr.isStdio() && runtime.getInstanceConfig().isMain()) {
// IO is stdio and JRuby was started through Main, use JVM console status
return Convert.asBoolean(context, JVMConsole.isTerminal);
Expand Down
43 changes: 24 additions & 19 deletions core/src/main/java/org/jruby/util/io/OpenFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ public static interface Finalizer {
public void finalize(Ruby runtime, OpenFile fptr, boolean noraise);
}

private ChannelFD fd;
// volatile so a close in another thread is visible to the accessors below, which read fd
// outside the lock, and so the ChannelFD they hand out is safely published.
private volatile ChannelFD fd;
private int mode;
private long pid = -1;
private Process process;
Expand Down Expand Up @@ -576,6 +578,8 @@ public boolean waitReadable(ThreadContext context) {
public boolean ready(Ruby runtime, RubyThread thread, int ops, long timeout) {
boolean locked = lock();
try {
ChannelFD fd = checkedFD();

if (fd.chSelect != null) {
int realOps = ops & fd.chSelect.validOps();

Expand Down Expand Up @@ -749,6 +753,16 @@ public void checkClosed() {
}
}

/**
* Read the fd field once and then check it, so a concurrent close cannot null it between the
* check and the dereference and turn a closed-stream IOError into a NullPointerException.
*/
private ChannelFD checkedFD() {
ChannelFD fd = this.fd;
if (fd == null) throw runtime.newIOError(RubyIO.CLOSED_STREAM_MSG);
return fd;
}

public boolean isBinmode() {
return (mode & BINMODE) != 0;
}
Expand Down Expand Up @@ -2580,38 +2594,31 @@ public ChannelFD fd() {
public Channel channel() {
// MRI equivalent: rb_io_check_closed(fptr) + fptr->fd access in io.c
// when an IO was closed from another thread MRI raises IOError("closed stream") via io_fd_check_closed (io.c)
checkClosed();
return fd.ch;
return checkedFD().ch;
}

public ReadableByteChannel readChannel() {
checkClosed();
return fd.chRead;
return checkedFD().chRead;
}

public WritableByteChannel writeChannel() {
checkClosed();
return fd.chWrite;
return checkedFD().chWrite;
}

public SeekableByteChannel seekChannel() {
checkClosed();
return fd.chSeek;
return checkedFD().chSeek;
}

public SelectableChannel selectChannel() {
checkClosed();
return fd.chSelect;
return checkedFD().chSelect;
}

public FileChannel fileChannel() {
checkClosed();
return fd.chFile;
return checkedFD().chFile;
}

public SocketChannel socketChannel() {
checkClosed();
return fd.chSock;
return checkedFD().chSock;
}

IRubyObject finishWriteconv(ThreadContext context, boolean noalloc) {
Expand Down Expand Up @@ -2705,9 +2712,7 @@ public void setBlocking(Ruby runtime, boolean blocking) {
// and make those channels act like non-blocking
nonblock = !blocking;

ChannelFD fd = this.fd;

checkClosed();
ChannelFD fd = checkedFD();

if (fd.chSelect != null) {
try {
Expand Down Expand Up @@ -2818,7 +2823,7 @@ public static int getFModeFromString(String modesString) throws InvalidValueExce
}

public int getFileno() {
return fd.bestFileno(true);
return checkedFD().bestFileno(true);
}

// rb_thread_flock
Expand Down
Loading