Skip to content

Commit 3b698b1

Browse files
committed
[GR-17131] Detect when starting a Thread fails and report the exception to the Thread spawning the new Thread.
PullRequest: truffleruby/952
2 parents da6a94c + af2a149 commit 3b698b1

File tree

7 files changed

+72
-19
lines changed

7 files changed

+72
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Bug fixes:
1212
* Do not modify the argument passed to `IO#write` when the encoding does not match (#1714).
1313
* Use the class where the method was defined to check if an `UnboundMethod` can be used for `#define_method` (#1710).
1414
* Fixed setting `$~` for `Enumerable` and `Enumerator::Lazy`'s `#grep` and `#grep_v`.
15+
* Improved errors when interacting with single-threaded languages (#1709).
1516

1617
Compatibility:
1718

src/main/java/org/truffleruby/core/VMPrimitiveNodes.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.truffleruby.language.yield.YieldNode;
7777
import org.truffleruby.platform.Signals;
7878

79+
import java.io.PrintStream;
7980
import java.util.Map.Entry;
8081

8182
@CoreClass(value = "VM primitives")
@@ -317,7 +318,16 @@ public boolean watchSignalProc(DynamicObject signalNameString, DynamicObject pro
317318
// Workaround: we need to register with Truffle (which means going multithreaded),
318319
// so that NFI can get its context to call pthread_kill() (GR-7405).
319320
final TruffleContext truffleContext = context.getEnv().getContext();
320-
final Object prev = truffleContext.enter();
321+
final Object prev;
322+
try {
323+
prev = truffleContext.enter();
324+
} catch (IllegalStateException e) { // Multi threaded access denied from Truffle
325+
// Not in a context, so we cannot use TruffleLogger
326+
final PrintStream printStream = new PrintStream(context.getEnv().err(), true);
327+
printStream.println("[ruby] SEVERE: signal " + signalName + " caught but can't create a thread to handle it so ignoring and restoring the default handler");
328+
Signals.restoreDefaultHandler(signalName);
329+
return;
330+
}
321331
try {
322332
context.getSafepointManager().pauseAllThreadsAndExecuteFromNonRubyThread(true, (rubyThread, currentNode) -> {
323333
if (rubyThread == rootThread &&

src/main/java/org/truffleruby/core/exception/ExceptionOperations.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.truffleruby.core.thread.ThreadNodes.ThreadGetExceptionNode;
1818
import org.truffleruby.language.RubyGuards;
1919
import org.truffleruby.language.backtrace.Backtrace;
20+
import org.truffleruby.language.control.JavaException;
2021
import org.truffleruby.language.control.RaiseException;
2122

2223
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -89,4 +90,14 @@ public static DynamicObject getFormatter(String name, RubyContext context) {
8990
return (DynamicObject) Layouts.MODULE.getFields(context.getCoreLibrary().getTruffleExceptionOperationsModule()).getConstant(name).getValue();
9091
}
9192

93+
public static void rethrow(Throwable throwable) {
94+
if (throwable instanceof RuntimeException) {
95+
throw (RuntimeException) throwable;
96+
} else if (throwable instanceof Error) {
97+
throw (Error) throwable;
98+
} else {
99+
throw new JavaException(throwable);
100+
}
101+
}
102+
92103
}

src/main/java/org/truffleruby/core/fiber/FiberLayout.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ DynamicObject createFiber(DynamicObjectFactory factory,
3535
@Volatile @Nullable DynamicObject lastResumedByFiber,
3636
@Volatile boolean alive,
3737
@Nullable Thread thread,
38-
@Volatile boolean transferred);
38+
@Volatile boolean transferred,
39+
@Volatile @Nullable Throwable uncaughtException);
3940

4041
boolean isFiber(DynamicObject object);
4142

@@ -64,4 +65,7 @@ DynamicObject createFiber(DynamicObjectFactory factory,
6465
boolean getTransferred(DynamicObject object);
6566
void setTransferred(DynamicObject object, boolean value);
6667

68+
Throwable getUncaughtException(DynamicObject object);
69+
void setUncaughtException(DynamicObject object, Throwable value);
70+
6771
}

src/main/java/org/truffleruby/core/fiber/FiberManager.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.truffleruby.Layouts;
2020
import org.truffleruby.RubyContext;
2121
import org.truffleruby.core.array.ArrayHelpers;
22+
import org.truffleruby.core.exception.ExceptionOperations;
2223
import org.truffleruby.core.proc.ProcOperations;
2324
import org.truffleruby.core.thread.ThreadManager;
2425
import org.truffleruby.core.thread.ThreadManager.BlockingAction;
@@ -113,7 +114,8 @@ public DynamicObject createFiber(RubyContext context, DynamicObject thread, Dyna
113114
null,
114115
true,
115116
null,
116-
false);
117+
false,
118+
null);
117119
}
118120

119121
@TruffleBoundary
@@ -122,9 +124,13 @@ private static LinkedBlockingQueue<FiberMessage> newMessageQueue() {
122124
}
123125

124126
public void initialize(DynamicObject fiber, DynamicObject block, Node currentNode) {
125-
context.getThreadManager().spawnFiber(() -> fiberMain(context, fiber, block, currentNode));
126-
127-
waitForInitialization(context, fiber, currentNode);
127+
ThreadManager.FIBER_BEING_SPAWNED.set(fiber);
128+
try {
129+
context.getThreadManager().spawnFiber(() -> fiberMain(context, fiber, block, currentNode));
130+
waitForInitialization(context, fiber, currentNode);
131+
} finally {
132+
ThreadManager.FIBER_BEING_SPAWNED.remove();
133+
}
128134
}
129135

130136
/** Wait for full initialization of the new fiber */
@@ -135,6 +141,11 @@ public static void waitForInitialization(RubyContext context, DynamicObject fibe
135141
initializedLatch.await();
136142
return BlockingAction.SUCCESS;
137143
});
144+
145+
final Throwable uncaughtException = Layouts.FIBER.getUncaughtException(fiber);
146+
if (uncaughtException != null) {
147+
ExceptionOperations.rethrow(uncaughtException);
148+
}
138149
}
139150

140151
private static final BranchProfile UNPROFILED = BranchProfile.create();

src/main/java/org/truffleruby/core/mutex/MutexOperations.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515

1616
import org.truffleruby.Layouts;
1717
import org.truffleruby.RubyContext;
18+
import org.truffleruby.core.exception.ExceptionOperations;
1819
import org.truffleruby.core.thread.ThreadManager;
1920
import org.truffleruby.language.RubyNode;
20-
import org.truffleruby.language.control.JavaException;
2121
import org.truffleruby.language.control.RaiseException;
2222

2323
import java.util.concurrent.locks.ReentrantLock;
@@ -83,13 +83,7 @@ protected static void internalLockEvenWithException(ReentrantLock lock, RubyNode
8383
}
8484

8585
if (throwable != null) {
86-
if (throwable instanceof RuntimeException) {
87-
throw (RuntimeException) throwable;
88-
} else if (throwable instanceof Error) {
89-
throw (Error) throwable;
90-
} else {
91-
throw new JavaException(throwable);
92-
}
86+
ExceptionOperations.rethrow(throwable);
9387
}
9488
}
9589

src/main/java/org/truffleruby/core/thread/ThreadManager.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public ThreadManager(RubyContext context) {
8686
this.context = context;
8787
this.rootThread = createBootThread("main");
8888
this.rootJavaThread = Thread.currentThread();
89-
this.fiberPool = Executors.newCachedThreadPool(this::createJavaThread);
89+
this.fiberPool = Executors.newCachedThreadPool(this::createFiberJavaThread);
9090
}
9191

9292
public void initialize(TruffleNFIPlatform nfi, NativeConfiguration nativeConfiguration) {
@@ -118,7 +118,16 @@ public void restartMainThread(Thread mainJavaThread) {
118118
Layouts.FIBER.setFinishedLatch(rootFiber, new CountDownLatch(1));
119119
}
120120

121-
public Thread createJavaThread(Runnable runnable) {
121+
// spawning Thread => Fiber object
122+
public static final ThreadLocal<DynamicObject> FIBER_BEING_SPAWNED = new ThreadLocal<>();
123+
124+
private Thread createFiberJavaThread(Runnable runnable) {
125+
DynamicObject fiber = FIBER_BEING_SPAWNED.get();
126+
assert fiber != null;
127+
return createJavaThread(runnable, fiber);
128+
}
129+
130+
private Thread createJavaThread(Runnable runnable, DynamicObject fiber) {
122131
if (context.getOptions().SINGLE_THREADED) {
123132
throw new RaiseException(context, context.getCoreExceptions().securityError("threads not allowed in single-threaded mode", null));
124133
}
@@ -128,6 +137,18 @@ public Thread createJavaThread(Runnable runnable) {
128137
}
129138

130139
final Thread thread = context.getEnv().createThread(runnable);
140+
141+
assert fiber != null;
142+
thread.setUncaughtExceptionHandler((javaThread, throwable) -> {
143+
try {
144+
Layouts.FIBER.setUncaughtException(fiber, throwable);
145+
Layouts.FIBER.getInitializedLatch(fiber).countDown();
146+
} catch (Throwable t) {
147+
t.initCause(throwable);
148+
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(javaThread, t);
149+
}
150+
});
151+
131152
rubyManagedThreads.add(thread);
132153
return thread;
133154
}
@@ -233,12 +254,13 @@ public void initialize(DynamicObject rubyThread, Node currentNode, String info,
233254
startSharing(rubyThread, sharingReason);
234255

235256
Layouts.THREAD.setSourceLocation(rubyThread, info);
257+
final DynamicObject rootFiber = Layouts.THREAD.getFiberManager(rubyThread).getRootFiber();
236258

237-
final Thread thread = createJavaThread(() -> threadMain(rubyThread, currentNode, task));
259+
final Thread thread = createJavaThread(() -> threadMain(rubyThread, currentNode, task), rootFiber);
238260
thread.setName(NAME_PREFIX + " id=" + thread.getId() + " from " + info);
239-
thread.start();
240261

241-
FiberManager.waitForInitialization(context, Layouts.THREAD.getFiberManager(rubyThread).getRootFiber(), currentNode);
262+
thread.start();
263+
FiberManager.waitForInitialization(context, rootFiber, currentNode);
242264
}
243265

244266
private void threadMain(DynamicObject thread, Node currentNode, Supplier<Object> task) {

0 commit comments

Comments
 (0)