Skip to content

Commit cea8768

Browse files
committed
[GR-7405] We no longer need to enter the TruffleContext to interrupt other threads, except for rb_thread_call_without_gvl()
* All other UnblockingAction are fine to execute without being inside the context.
1 parent eafbc16 commit cea8768

File tree

5 files changed

+49
-51
lines changed

5 files changed

+49
-51
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ New features:
44

55
* Access to local variables of the interactive Binding via language bindings is now supported: `context.getBindings("ruby").putMember("my_var", 42);` (#2030).
66
* `VALUE`s in C extensions now expose the Ruby object when viewed in the debugger, as long as they have not been converted to native values.
7+
* Signal handlers can now be run without triggering multi-threading.
78

89
Bug fixes:
910

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

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,12 @@
8080

8181
import com.oracle.truffle.api.CompilerDirectives;
8282
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
83-
import com.oracle.truffle.api.TruffleContext;
8483
import com.oracle.truffle.api.dsl.Cached;
8584
import com.oracle.truffle.api.dsl.Specialization;
8685
import com.oracle.truffle.api.frame.VirtualFrame;
8786
import com.oracle.truffle.api.profiles.BranchProfile;
8887
import com.oracle.truffle.api.profiles.ConditionProfile;
8988

90-
import sun.misc.Signal;
9189
import sun.misc.SignalHandler;
9290

9391
@CoreModule(value = "VMPrimitives", isClass = true)
@@ -260,48 +258,16 @@ protected boolean watchSignalProc(Object signalString, RubyProc action,
260258
}
261259

262260
final String signalName = libSignalString.getJavaString(signalString);
263-
return registerHandler(signalName, signal -> {
264-
if (context.getOptions().SINGLE_THREADED) {
265-
warnRestoreAndRaise(context, signalName, signal, "create a thread");
266-
return;
267-
}
268261

262+
return registerHandler(signalName, signal -> {
269263
final RubyThread rootThread = context.getThreadManager().getRootThread();
270-
271-
// Workaround: we need to register with Truffle (which means going multithreaded),
272-
// so that NFI can get its context to call pthread_kill() (GR-7405).
273-
final TruffleContext truffleContext = context.getEnv().getContext();
274-
final Object prev;
275-
try {
276-
prev = truffleContext.enter(this);
277-
} catch (IllegalStateException e) { // Multi threaded access denied from Truffle
278-
warnRestoreAndRaise(context, signalName, signal, "attach a thread");
279-
return;
280-
}
281-
try {
282-
context.getSafepointManager().pauseAllThreadsAndExecuteFromNonRubyThread(
283-
"Handling of signal " + signal,
284-
SafepointPredicate.currentFiberOfThread(context, rootThread),
285-
(rubyThread, currentNode) -> ProcOperations.rootCall(action));
286-
} finally {
287-
truffleContext.leave(this, prev);
288-
}
264+
context.getSafepointManager().pauseAllThreadsAndExecuteFromNonRubyThread(
265+
"Handling of signal " + signal,
266+
SafepointPredicate.currentFiberOfThread(context, rootThread),
267+
(rubyThread, currentNode) -> ProcOperations.rootCall(action));
289268
});
290269
}
291270

292-
private static void warnRestoreAndRaise(RubyContext context, String signalName, Signal signal, String failure) {
293-
// Not in a context, so we cannot use TruffleLogger
294-
context.getEnvErrStream().println(
295-
"[ruby] SEVERE: signal " + signal + " caught but can't " + failure +
296-
" to handle it so restoring the default handler and re-raising the signal");
297-
Signals.restoreDefaultHandler(signalName);
298-
try {
299-
Signal.raise(signal);
300-
} catch (IllegalArgumentException illegalArgumentException) {
301-
illegalArgumentException.printStackTrace(context.getEnvErrStream());
302-
}
303-
}
304-
305271
@TruffleBoundary
306272
private boolean restoreDefaultHandler(String signalName) {
307273
if (getContext().getOptions().EMBEDDED) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,11 @@ public RubyThread getCurrentThread() {
536536
return rubyThread;
537537
}
538538

539+
@TruffleBoundary
540+
public RubyThread getCurrentThreadOrNull() {
541+
return currentThread.get();
542+
}
543+
539544
@TruffleBoundary
540545
public RubyFiber getRubyFiberFromCurrentJavaThread() {
541546
return rubyFiber.get();

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.Queue;
4444
import java.util.concurrent.TimeUnit;
4545

46+
import com.oracle.truffle.api.TruffleContext;
4647
import com.oracle.truffle.api.profiles.ConditionProfile;
4748
import org.jcodings.specific.USASCIIEncoding;
4849
import org.jcodings.specific.UTF8Encoding;
@@ -558,7 +559,7 @@ protected Object call(RubyThread thread, Object function, Object arg, Object unb
558559
if (unblocker == nil) {
559560
unblockingAction = threadManager.getNativeCallUnblockingAction();
560561
} else {
561-
unblockingAction = makeUnblockingAction(unblocker, unblockerArg);
562+
unblockingAction = makeUnblockingAction(getContext(), unblocker, unblockerArg);
562563
}
563564

564565
final UnblockingActionHolder actionHolder = threadManager.getActionHolder(Thread.currentThread());
@@ -574,13 +575,37 @@ protected Object call(RubyThread thread, Object function, Object arg, Object unb
574575
}
575576

576577
@TruffleBoundary
577-
private UnblockingAction makeUnblockingAction(Object function, Object argument) {
578+
private static UnblockingAction makeUnblockingAction(RubyContext context, Object function, Object argument) {
578579
assert InteropLibrary.getUncached().isExecutable(function);
580+
579581
return () -> {
582+
final TruffleContext truffleContext = context.getEnv().getContext();
583+
final boolean alreadyEntered = truffleContext.isEntered();
584+
Object prev = null;
585+
if (!alreadyEntered) {
586+
// We need to enter the context to execute this unblocking action, as it runs on Sulong.
587+
try {
588+
if (context.getOptions().SINGLE_THREADED) {
589+
throw new IllegalStateException("--single-threaded was passed");
590+
}
591+
prev = truffleContext.enter(null);
592+
} catch (IllegalStateException e) { // Multi threaded access denied from Truffle
593+
// Not in a context, so we cannot use TruffleLogger
594+
context.getEnvErrStream().println(
595+
"[ruby] SEVERE: could not unblock thread inside blocking call in C extension because " +
596+
"the context does not allow multithreading (" + e.getMessage() + ")");
597+
return;
598+
}
599+
}
600+
580601
try {
581602
InteropLibrary.getUncached().execute(function, argument);
582603
} catch (InteropException e) {
583604
throw CompilerDirectives.shouldNotReachHere(e);
605+
} finally {
606+
if (!alreadyEntered) {
607+
truffleContext.leave(null, prev);
608+
}
584609
}
585610
};
586611
}

src/main/java/org/truffleruby/language/SafepointManager.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,6 @@ private void assumptionInvalidated(Node currentNode, boolean fromBlockingCall) {
144144
private SafepointAction step(Node currentNode, boolean isDrivingThread, String reason) {
145145
assert isDrivingThread == lock.isHeldByCurrentThread();
146146

147-
final RubyThread thread = context.getThreadManager().getCurrentThread();
148-
149147
// Wait for other threads to reach their safepoint
150148
if (isDrivingThread) {
151149
driveArrivalAtPhaser(reason);
@@ -162,7 +160,9 @@ private SafepointAction step(Node currentNode, boolean isDrivingThread, String r
162160
SafepointAction deferredAction = null;
163161

164162
try {
165-
if (filter.test(thread)) {
163+
final RubyThread thread = context.getThreadManager().getCurrentThreadOrNull();
164+
165+
if (thread != null && filter.test(thread)) {
166166
if (SafepointAction.isDeferred(action)) {
167167
deferredAction = action;
168168
} else {
@@ -196,8 +196,9 @@ private void driveArrivalAtPhaser(String reason) {
196196
// retry
197197
} catch (TimeoutException e) {
198198
if (System.nanoTime() >= max) {
199-
RubyLanguage.LOGGER.severe(String.format(
200-
"waited %d seconds in the SafepointManager but %d of %d threads did not arrive - a thread is likely making a blocking call - reason for the safepoint: %s",
199+
// Possibly not in a context, so we cannot use TruffleLogger
200+
context.getEnvErrStream().println(String.format(
201+
"[ruby] SEVERE: waited %d seconds in the SafepointManager but %d of %d threads did not arrive - a thread is likely making a blocking call - reason for the safepoint: %s",
201202
waits * WAIT_TIME_IN_SECONDS,
202203
phaser.getUnarrivedParties(),
203204
phaser.getRegisteredParties(),
@@ -207,8 +208,8 @@ private void driveArrivalAtPhaser(String reason) {
207208
restoreDefaultInterruptHandler();
208209
}
209210
if (max >= exitTime) {
210-
RubyLanguage.LOGGER.severe(
211-
"waited " + MAX_WAIT_TIME_IN_SECONDS +
211+
context.getEnvErrStream().println(
212+
"[ruby] SEVERE: waited " + MAX_WAIT_TIME_IN_SECONDS +
212213
" seconds in the SafepointManager, terminating the process as it is unlikely to get unstuck");
213214
System.exit(1);
214215
}
@@ -253,12 +254,12 @@ private void printStacktracesOfBlockedThreads() {
253254
}
254255

255256
private void restoreDefaultInterruptHandler() {
256-
RubyLanguage.LOGGER.warning("restoring default interrupt handler");
257-
257+
// Possibly not in a context, so we cannot use TruffleLogger
258+
context.getEnvErrStream().println("[ruby] WARNING: restoring default interrupt handler");
258259
try {
259260
Signals.restoreDefaultHandler("INT");
260261
} catch (Throwable t) {
261-
RubyLanguage.LOGGER.warning("failed to restore default interrupt handler\n" + t);
262+
context.getEnvErrStream().println("[ruby] WARNING: failed to restore default interrupt handler\n" + t);
262263
}
263264
}
264265

0 commit comments

Comments
 (0)