Skip to content

Commit 39cd7eb

Browse files
committed
Avoid re-polling for events when printing error
Dynamically printing error output to the configured stderr stream may trigger additional thread interrupt polls, wiping out any thread interrupt currently being reported or propagated. This patch avoids such polls by writing directly to the underlying IO without using Ruby logic if and only if it is the original (boot) stderr stream. This also removes a second poll that happens when exiting the blocking task for a raise, a side effect of using afterBlockingCall to set the new status. The status update happens again after this point, and the poll is spurious. Fixes jruby#8479
1 parent eee97c9 commit 39cd7eb

File tree

3 files changed

+94
-12
lines changed

3 files changed

+94
-12
lines changed

core/src/main/java/org/jruby/Ruby.java

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,15 @@
172172
import java.io.InputStream;
173173
import java.io.PrintStream;
174174
import java.io.PrintWriter;
175+
import java.io.Writer;
175176
import java.lang.invoke.MethodHandle;
176177
import java.lang.ref.WeakReference;
177178
import java.net.BindException;
179+
import java.nio.ByteBuffer;
180+
import java.nio.channels.Channels;
181+
import java.nio.channels.WritableByteChannel;
178182
import java.nio.charset.Charset;
183+
import java.nio.charset.StandardCharsets;
179184
import java.nio.charset.UnsupportedCharsetException;
180185
import java.security.SecureRandom;
181186
import java.util.ArrayList;
@@ -2848,6 +2853,25 @@ WarnCallback getRegexpWarnings() {
28482853
return regexpWarnings;
28492854
}
28502855

2856+
public IRubyObject getStderr() {
2857+
return getGlobalVariables().get("$stderr");
2858+
}
2859+
2860+
/**
2861+
* Return the original stderr with which this runtime was initialized.
2862+
*
2863+
* Used for fast-path comparisons when printing error info directly to stderr.
2864+
*
2865+
* @return the original stderr with which this runtime was initialized
2866+
*/
2867+
public IRubyObject getOriginalStderr() {
2868+
return originalStderr;
2869+
}
2870+
2871+
void setOriginalStderr(IRubyObject stderr) {
2872+
this.originalStderr = stderr;
2873+
}
2874+
28512875
public PrintStream getErrorStream() {
28522876
// FIXME: We can't guarantee this will always be a RubyIO...so the old code here is not safe
28532877
/*java.io.OutputStream os = ((RubyIO) getGlobalVariables().getService("$stderr")).getOutStream();
@@ -2916,36 +2940,41 @@ private static boolean isJavaPackageOrJavaClassProxyType(final RubyModule type)
29162940
return type instanceof JavaPackage || ClassUtils.isJavaClassProxyType(type);
29172941
}
29182942

2919-
/** Prints an error with backtrace to the error stream.
2943+
/**
2944+
* Prints a Ruby exception with backtrace to the configured stderr stream.
29202945
*
29212946
* MRI: eval.c - error_print()
29222947
*
29232948
*/
29242949
public void printError(final RubyException ex) {
29252950
if (ex == null) return;
29262951

2927-
PrintStream errorStream = getErrorStream();
2928-
String backtrace = config.getTraceType().printBacktrace(ex, (errorStream == System.err) && getPosix().isatty(FileDescriptor.err));
2929-
try {
2930-
errorStream.print(backtrace);
2931-
} catch (Exception e) {
2932-
System.err.print(backtrace);
2933-
}
2952+
boolean formatted =
2953+
getStderr() == getOriginalStderr() &&
2954+
getErr() == System.err &&
2955+
getPosix().isatty(FileDescriptor.err);
2956+
2957+
String backtrace = config.getTraceType().printBacktrace(ex, formatted);
2958+
printErrorString(backtrace);
29342959
}
29352960

2961+
/**
2962+
* Prints an exception to System.err.
2963+
*
2964+
* @param ex
2965+
*/
29362966
public void printError(final Throwable ex) {
29372967
if (ex instanceof RaiseException) {
29382968
printError(((RaiseException) ex).getException());
29392969
return;
29402970
}
29412971

29422972
ByteArrayOutputStream baos = new ByteArrayOutputStream();
2943-
PrintStream errorStream = getErrorStream();
29442973

29452974
ex.printStackTrace(new PrintStream(baos));
29462975

29472976
try {
2948-
errorStream.write(baos.toByteArray());
2977+
printErrorString(baos.toByteArray());
29492978
} catch (Exception e) {
29502979
try {
29512980
System.err.write(baos.toByteArray());
@@ -2956,6 +2985,50 @@ public void printError(final Throwable ex) {
29562985
}
29572986
}
29582987

2988+
/**
2989+
* Prints a string directly to the stderr channel, if default, or via dynamic dispatch otherwise.
2990+
*
2991+
* @param msg the string to print
2992+
*/
2993+
public void printErrorString(String msg) {
2994+
IRubyObject stderr = getStderr();
2995+
2996+
WritableByteChannel writeChannel;
2997+
if (stderr == getOriginalStderr() &&
2998+
(writeChannel = ((RubyIO) stderr).getOpenFile().fd().chWrite) != null) {
2999+
Writer writer = Channels.newWriter(writeChannel, "UTF-8");
3000+
try {
3001+
writer.write(msg);
3002+
writer.flush();
3003+
} catch (IOException ioe) {
3004+
// ignore as in CRuby
3005+
}
3006+
} else {
3007+
getErrorStream().print(msg);
3008+
}
3009+
}
3010+
3011+
/**
3012+
* Prints a string directly to the stderr channel, if default, or via dynamic dispatch otherwise.
3013+
*
3014+
* @param msg the string to print
3015+
*/
3016+
public void printErrorString(byte[] msg) {
3017+
IRubyObject stderr = getGlobalVariables().get("$stderr");
3018+
3019+
try {
3020+
WritableByteChannel writeChannel;
3021+
if (stderr == getOriginalStderr() &&
3022+
(writeChannel = ((RubyIO) stderr).getOpenFile().fd().chWrite) != null) {
3023+
writeChannel.write(ByteBuffer.wrap(msg));
3024+
} else {
3025+
getErrorStream().write(msg);
3026+
}
3027+
} catch (IOException ioe) {
3028+
// ignore as in CRuby
3029+
}
3030+
}
3031+
29593032
static final String ROOT_FRAME_NAME = "(root)";
29603033
static long yarpTime = 0;
29613034
static boolean loaded = false;
@@ -5724,6 +5797,8 @@ public void warn(String message) {
57245797
private final EnumMap<DefinedMessage, RubyString> definedMessages = new EnumMap<>(DefinedMessage.class);
57255798
private final EnumMap<RubyThread.Status, RubyString> threadStatuses = new EnumMap<>(RubyThread.Status.class);
57265799

5800+
private IRubyObject originalStderr;
5801+
57275802
public interface ObjectSpacer {
57285803
void addToObjectSpace(Ruby runtime, boolean useObjectSpace, IRubyObject object);
57295804
}

core/src/main/java/org/jruby/RubyGlobal.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ public static void initSTDIO(Ruby runtime, GlobalVariables globals) {
311311
runtime.defineGlobalConstant("STDIN", stdin);
312312
runtime.defineGlobalConstant("STDOUT", stdout);
313313
runtime.defineGlobalConstant("STDERR", stderr);
314+
315+
runtime.setOriginalStderr(stderr);
314316
} else {
315317
((RubyIO) runtime.getObject().getConstant("STDIN")).getOpenFile().setFD(stdin.getOpenFile().fd());
316318
((RubyIO) runtime.getObject().getConstant("STDOUT")).getOpenFile().setFD(stdout.getOpenFile().fd());

core/src/main/java/org/jruby/RubyThread.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@
3434
package org.jruby;
3535

3636
import java.io.IOException;
37+
import java.io.Writer;
3738
import java.lang.management.ManagementFactory;
3839
import java.lang.ref.WeakReference;
3940
import java.nio.ByteBuffer;
4041
import java.nio.channels.Channel;
42+
import java.nio.channels.Channels;
4143
import java.nio.channels.SelectableChannel;
4244
import java.nio.channels.SelectionKey;
4345
import java.nio.channels.Selector;
46+
import java.nio.channels.WritableByteChannel;
47+
import java.nio.charset.StandardCharsets;
4448
import java.util.Iterator;
4549
import java.util.Objects;
4650
import java.util.Queue;
@@ -275,7 +279,6 @@ private void executeInterrupts(ThreadContext context, boolean blockingTiming) {
275279
((RubyFixnum) err).getLongValue() == 2)) {
276280
toKill();
277281
} else {
278-
afterBlockingCall();
279282
if (getStatus() == Status.SLEEP) {
280283
exitSleep();
281284
}
@@ -2071,7 +2074,9 @@ public void exceptionRaised(RaiseException exception) {
20712074
protected void printReportExceptionWarning() {
20722075
Ruby runtime = getRuntime();
20732076
String name = threadImpl.getReportName();
2074-
runtime.getErrorStream().println("warning: thread \"" + name + "\" terminated with exception (report_on_exception is true):");
2077+
String warning = "warning: thread \"" + name + "\" terminated with exception (report_on_exception is true):";
2078+
2079+
runtime.printErrorString(warning);
20752080
}
20762081

20772082
/**

0 commit comments

Comments
 (0)