Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: bed76e78e75a
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: b1df1bac53ac
Choose a head ref
  • 2 commits
  • 4 files changed
  • 1 contributor

Commits on Nov 18, 2016

  1. Implement Thread{.,#}report_on_exception[=].

    This impl works as follows:
    
    * Default global value is nil.
    * nil = report when thread is GC if nobody has captured the
      exception (i.e. called #join or #value or abort_on_exception
      logic has fired).
    * true = report when the thread terminates, regardless of capture.
    * false = never report.
    * New threads inherit the current global setting.
    * If a thread name has been set from Ruby, it will be combined
      with JRuby's internal name for the report. If it has not been
      set, we will just report the internal thread name.
    
    There are some open questions for this feature:
    
    * If join/value are interrupted, should they still set the capture
      bit? My impl does not; they must complete.
    * Is the VERBOSE-like nil/true/false clear enough or should we use
      symbols like :off, :gc, :on?
    headius committed Nov 18, 2016
    Copy the full SHA
    e408638 View commit details
  2. Copy the full SHA
    b1df1ba View commit details
11 changes: 11 additions & 0 deletions core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
@@ -1439,6 +1439,8 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
falseObject.setFrozen(true);
trueObject = new RubyBoolean.True(this);
trueObject.setFrozen(true);

reportOnException = nilObject;
}

private void initCore() {
@@ -4268,6 +4270,14 @@ public void setGlobalAbortOnExceptionEnabled(boolean enable) {
globalAbortOnExceptionEnabled = enable;
}

public IRubyObject getReportOnException() {
return reportOnException;
}

public void setReportOnException(IRubyObject enable) {
reportOnException = enable;
}

public boolean isDoNotReverseLookupEnabled() {
return doNotReverseLookupEnabled;
}
@@ -5099,6 +5109,7 @@ public IRubyObject call(ThreadContext context, RecursiveFunction func, IRubyObje
private volatile EventHook[] eventHooks = EMPTY_HOOKS;
private boolean hasEventHooks;
private boolean globalAbortOnExceptionEnabled = false;
private IRubyObject reportOnException;
private boolean doNotReverseLookupEnabled = false;
private volatile boolean objectSpaceEnabled;
private boolean siphashEnabled;
140 changes: 85 additions & 55 deletions core/src/main/java/org/jruby/RubyThread.java
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@
package org.jruby;

import java.io.IOException;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.nio.channels.Channel;
import java.nio.channels.SelectableChannel;
@@ -58,6 +59,7 @@
import java.util.concurrent.locks.Lock;

import org.jcodings.Encoding;
import org.jcodings.specific.USASCIIEncoding;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
@@ -120,10 +122,15 @@ public class RubyThread extends RubyObject implements ExecutionContext {
/** Whether this thread should try to abort the program on exception */
private volatile boolean abortOnException;

/** Whether this thread should report_on_exception when this thread GCs, when it terminates, or never */
private volatile IRubyObject reportOnException;

/** Whether this thread's terminating exception has been captured by any code *after* the thread terminated. */
private volatile boolean exceptionCaptured;

/** The final value resulting from the thread's execution */
private volatile IRubyObject finalResult;

private volatile IRubyObject threadName;
private String file; private int line; // Thread.new location (for inspect)

/**
@@ -213,7 +220,7 @@ protected RubyThread(Ruby runtime, RubyClass type) {
super(runtime, type);

finalResult = errorInfo = runtime.getNil();
threadName = runtime.getNil();
reportOnException = runtime.getReportOnException();
}

public RubyThread(Ruby runtime, RubyClass klass, Runnable runnable) {
@@ -577,7 +584,7 @@ private IRubyObject startThread(ThreadContext context, Runnable runnable) throws
thread.setDaemon(true);
this.file = context.getFile();
this.line = context.getLine();
setThreadName(runtime, thread, file, line, true);
initThreadName(runtime, thread, file, line);
threadImpl = new NativeThread(this, thread);

addToCorrectThreadGroup(context);
@@ -606,44 +613,27 @@ private IRubyObject startThread(ThreadContext context, Runnable runnable) throws

private static final String RUBY_THREAD_PREFIX = "Ruby-";

private void setThreadName(final Ruby runtime, final Thread thread,
final String file, final int line, final boolean newThread) {
private static void initThreadName(final Ruby runtime, final Thread thread, final String file, final int line) {
// "Ruby-0-Thread-16: (irb):21"
// "Ruby-0-Thread-17@worker#1: (irb):21"
final String newName;
final String setName = getNameOrNull();
final String currentName = thread.getName();
if ( currentName != null && currentName.startsWith(RUBY_THREAD_PREFIX) ) {
final int i = currentName.indexOf('@'); // Thread#name separator
if ( i == -1 ) { // name not set yet: "Ruby-0-Thread-42: FILE:LINE"
int end = currentName.indexOf(':');
if ( end == -1 ) end = currentName.length();
final String prefix = currentName.substring(0, end);
newName = currentName.replace(prefix, prefix + '@' + setName);

}
else { // name previously set: "Ruby-0-Thread-42@foo: FILE:LINE"
final String prefix = currentName.substring(0, i); // Ruby-0-Thread-42
int end = currentName.indexOf(':', i);
if ( end == -1 ) end = currentName.length();
final String prefixWithName = currentName.substring(0, end); // Ruby-0-Thread-42@foo:
newName = currentName.replace(prefixWithName, setName == null ? prefix : (prefix + '@' + setName));
}
}
else if ( newThread ) {
final StringBuilder name = new StringBuilder(24);
name.append(RUBY_THREAD_PREFIX).append(runtime.getRuntimeNumber());
name.append('-').append("Thread-").append(incAndGetThreadCount(runtime));
if ( setName != null ) name.append('@').append(setName);
if ( file != null ) { // in JIT we seem to get "" as file and line 0
name.append(':').append(' ').append(file).append(':').append(line + 1);
}
newName = name.toString();
}
else return; // not a new-thread that and does not match out Ruby- prefix
// ... very likely user-code set the java thread name - thus do not mess!
try { thread.setName(newName); }
catch (SecurityException ignore) { } // current thread can not modify
final StringBuilder name = new StringBuilder(24);
name
.append(RUBY_THREAD_PREFIX)
.append(runtime.getRuntimeNumber())
.append('-')
.append("Thread-")
.append(incAndGetThreadCount(runtime));
if ( file != null ) {
name
.append(':')
.append(' ')
.append(file)
.append(':')
.append(line + 1);
}
newName = name.toString();

thread.setName(newName);
}

// TODO likely makes sense to have a counter or the Ruby class directly (could be included with JMX)
@@ -785,21 +775,17 @@ public IRubyObject setName(IRubyObject name) {
if (!enc.isAsciiCompatible()) {
throw runtime.newArgumentError("ASCII incompatible encoding (" + enc + ")");
}
name = nameStr.newFrozen();
threadImpl.setRubyName(runtime.freezeAndDedupString(nameStr).asJavaString());
} else {
threadImpl.setRubyName(null);
}
this.threadName = name;
setThreadName(runtime, getNativeThread(), null, -1, false);

return name;
}

@JRubyMethod(name = "name")
public IRubyObject getName() {
return this.threadName;
}

private String getNameOrNull() {
final IRubyObject name = getName();
return ( name == null || name.isNil() ) ? null : name.asJavaString();
return (IRubyObject) threadImpl.getRubyName();
}

private boolean pendingInterruptInclude(IRubyObject err) {
@@ -1063,6 +1049,7 @@ public IRubyObject join(ThreadContext context, IRubyObject[] args) {
if (exitingException != null) {
// Set $! in the current thread before exiting
runtime.getGlobalVariables().set("$!", (IRubyObject)exitingException.getException());
exceptionCaptured = true;
throw exitingException;

}
@@ -1106,11 +1093,11 @@ public synchronized IRubyObject inspect() {
String cname = getMetaClass().getRealClass().getName();
part.append("#<").append(cname).append(':');
part.append(identityString());
final String name = getNameOrNull(); // thread.name
if ( name != null ) {
CharSequence name = threadImpl.getRubyName(); // thread.name
if (notEmpty(name)) {
part.append('@').append(name);
}
if ( file != null && file.length() > 0 && line >= 0 ) {
if (notEmpty(file) && line >= 0) {
part.append('@').append(file).append(':').append(line + 1);
}
part.append(' ');
@@ -1119,6 +1106,10 @@ public synchronized IRubyObject inspect() {
return getRuntime().newString(part.toString());
}

private boolean notEmpty(CharSequence str) {
return str != null && str.toString().length() > 0;
}

@JRubyMethod(name = "key?", required = 1)
public RubyBoolean key_p(IRubyObject key) {
key = getSymbolKey(key);
@@ -1584,6 +1575,39 @@ public IRubyObject backtrace_locations(ThreadContext context, IRubyObject[] args
return myContext.createCallerLocations(level, length, getNativeThread().getStackTrace());
}

@JRubyMethod(name = "report_on_exception=")
public IRubyObject report_on_exception_set(ThreadContext context, IRubyObject state) {
if (state.isNil()) {
reportOnException = state;
} else {
reportOnException = context.runtime.newBoolean(state.isTrue());
}
return this;
}

@JRubyMethod(name = "report_on_exception")
public IRubyObject report_on_exception(ThreadContext context) {
return reportOnException;
}

@JRubyMethod(name = "report_on_exception=", meta = true)
public static IRubyObject report_on_exception_set(ThreadContext context, IRubyObject self, IRubyObject state) {
Ruby runtime = context.runtime;

if (state.isNil()) {
runtime.setReportOnException(state);
} else {
runtime.setReportOnException(runtime.newBoolean(state.isTrue()));
}

return self;
}

@JRubyMethod(name = "report_on_exception", meta = true)
public static IRubyObject report_on_exception(ThreadContext context, IRubyObject self) {
return context.runtime.getReportOnException();
}

public StackTraceElement[] javaBacktrace() {
if (threadImpl instanceof NativeThread) {
return ((NativeThread)threadImpl).getThread().getStackTrace();
@@ -1604,17 +1628,22 @@ public void exceptionRaised(RaiseException exception) {
Ruby runtime = rubyException.getRuntime();
if (runtime.getSystemExit().isInstance(rubyException)) {
runtime.getThreadService().getMainThread().raise(rubyException);
}
else if (abortOnException(runtime)) {
} else if (abortOnException(runtime)) {
runtime.getThreadService().getMainThread().raise(rubyException);
return;
}
else if (runtime.isDebug()) {
} else if (reportOnException.isTrue()) {
printReportExceptionWarning();
runtime.printError(exception.getException());
}
exitingException = exception;
}

protected void printReportExceptionWarning() {
PrintStream errorStream = getRuntime().getErrorStream();
String name = threadImpl.getReportName();
errorStream.println("warning: thread \"" + name + "\" terminated with exception:");
}

/**
* For handling all non-Ruby exceptions bubbling out of threads
* @param exception
@@ -1631,6 +1660,7 @@ public void exceptionRaised(Throwable exception) {
Ruby runtime = getRuntime();
if (abortOnException(runtime) && exception instanceof Error) {
// re-propagate on main thread
exceptionCaptured = true;
runtime.getThreadService().getMainThread().raise(JavaUtil.convertJavaToUsableRubyObject(runtime, exception));
} else {
// just rethrow on this thread, let system handlers report it
55 changes: 54 additions & 1 deletion core/src/main/java/org/jruby/internal/runtime/NativeThread.java
Original file line number Diff line number Diff line change
@@ -39,7 +39,9 @@
public class NativeThread implements ThreadLike {
private final Reference<Thread> nativeThread;
public final RubyThread rubyThread;

public CharSequence rubyName;
public String nativeName;

@Deprecated
public NativeThread(RubyThread rubyThread, IRubyObject[] args, Block block) {
throw new RuntimeException();
@@ -48,6 +50,8 @@ public NativeThread(RubyThread rubyThread, IRubyObject[] args, Block block) {
public NativeThread(RubyThread rubyThread, Thread nativeThread) {
this.rubyThread = rubyThread;
this.nativeThread = new WeakReference<>(nativeThread);
this.rubyName = null;
this.nativeName = nativeThread.getName();
}

public void start() {
@@ -110,4 +114,53 @@ public String toString() {
public Thread nativeThread() {
return nativeThread.get();
}

@Override
public void setNativeName(String name) {
assert name != null;

Thread nativeThread = getThread();
if (nativeThread != null) {
try {
nativeThread.setName(name);
} catch (SecurityException se) {
// current thread cannot modify...ignore
}
}

this.nativeName = name;
}

@Override
public String getNativeName() {
return nativeName;
}

@Override
public void setRubyName(CharSequence name) {
this.rubyName = name;
}

@Override
public CharSequence getRubyName() {
return rubyName;
}

@Override
public String getReportName() {
String nativeName = getNativeName();

if (rubyName == null || rubyName.length() == 0) {
if (nativeName.equals("")) {
return "(unnamed)";
} else {
return nativeName;
}
} else {
if (nativeName.equals("")) {
return rubyName.toString();
}
return rubyName + " (" + nativeName + ")";
}
}
}
10 changes: 10 additions & 0 deletions core/src/main/java/org/jruby/internal/runtime/ThreadLike.java
Original file line number Diff line number Diff line change
@@ -49,4 +49,14 @@ public interface ThreadLike {
public boolean isInterrupted();

public Thread nativeThread();

public void setNativeName(String name);

public String getNativeName();

public void setRubyName(CharSequence name);

public CharSequence getRubyName();

public String getReportName();
}