Skip to content

Commit dd4bb57

Browse files
committed
[GR-7405] Use JNI to interrupt threads natively instead of Truffle NFI
* Truffle NFI needs to be inside a TruffleContext to work, but that is quite limiting.
1 parent d2b3b3d commit dd4bb57

File tree

6 files changed

+82
-55
lines changed

6 files changed

+82
-55
lines changed

mx.truffleruby/mx_truffleruby.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def contents(self, result):
105105
ruby_options = [
106106
'--experimental-options',
107107
'--building-core-cexts',
108+
'--platform-native-interrupt=false', # no librubysignal in the ruby home yet
108109
'--launcher=' + result,
109110
'--disable-gems',
110111
'--disable-rubyopt',
@@ -267,6 +268,6 @@ def verify_ci(args):
267268
'ruby_testdownstream_hello': [ruby_testdownstream_hello, ''],
268269
'ruby_testdownstream_sulong': [ruby_testdownstream_sulong, ''],
269270
'ruby_spotbugs': [ruby_spotbugs, ''],
270-
'verify-ci' : [verify_ci, '[options]'],
271+
'verify-ci': [verify_ci, '[options]'],
271272
'ruby_jacoco_args': [ruby_jacoco_args, ''],
272273
})

mx.truffleruby/suite.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
"mxversion": "5.281.0",
33
"name": "truffleruby",
44

5-
"imports" : {
6-
"suites" : [
5+
"imports": {
6+
"suites": [
77
{
88
"name": "regex",
99
"subdir": True,
1010
"version": "8c1abd10ad2992580462d7629db316177a256f82",
1111
"urls": [
12-
{"url": "https://github.com/oracle/graal.git", "kind" : "git"},
13-
{"url": "https://curio.ssw.jku.at/nexus/content/repositories/snapshots", "kind" : "binary"},
12+
{"url": "https://github.com/oracle/graal.git", "kind": "git"},
13+
{"url": "https://curio.ssw.jku.at/nexus/content/repositories/snapshots", "kind": "binary"},
1414
]
1515
},
1616
{
@@ -172,9 +172,20 @@
172172
"license": ["EPL-2.0"],
173173
},
174174

175+
"org.truffleruby.rubysignal": {
176+
"dir": "src/main/c/rubysignal",
177+
"native": "shared_lib",
178+
"deliverable": "rubysignal",
179+
"buildDependencies": [
180+
"org.truffleruby", # for the generated JNI header file
181+
],
182+
"cflags": ["-g", "-Wall", "-Werror"],
183+
},
184+
175185
"org.truffleruby": {
176186
"dir": "src/main",
177187
"sourceDirs": ["java"],
188+
"jniHeaders": True,
178189
"dependencies": [
179190
"truffleruby:TRUFFLERUBY-ANNOTATIONS",
180191
"truffleruby:TRUFFLERUBY-SHARED",
@@ -446,6 +457,7 @@
446457
"file:lib/cext/*.rb",
447458
"dependency:org.truffleruby.cext/src/main/c/truffleposix/<lib:truffleposix>",
448459
"dependency:org.truffleruby.cext/src/main/c/cext/<lib:truffleruby>",
460+
"dependency:org.truffleruby.rubysignal",
449461
],
450462
"lib/cext/include/": [
451463
"file:lib/cext/include/ccan",
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "org_truffleruby_signal_LibRubySignal.h"
2+
#include <pthread.h>
3+
#include <signal.h>
4+
5+
static void empty_handler(int sig) {
6+
}
7+
8+
JNIEXPORT jint JNICALL Java_org_truffleruby_signal_LibRubySignal_setupSIGVTALRMEmptySignalHandler(JNIEnv *env, jclass clazz) {
9+
struct sigaction action = {
10+
.sa_flags = 0, /* flags = 0 is intended as we want no SA_RESTART so we can interrupt blocking syscalls */
11+
.sa_handler = empty_handler,
12+
};
13+
return sigaction(SIGVTALRM, &action, NULL);
14+
}
15+
16+
JNIEXPORT jlong JNICALL Java_org_truffleruby_signal_LibRubySignal_threadID(JNIEnv *env, jclass clazz) {
17+
pthread_t pthread_id = pthread_self();
18+
return (jlong) pthread_id;
19+
}
20+
21+
JNIEXPORT jint JNICALL Java_org_truffleruby_signal_LibRubySignal_sendSIGVTALRMToThread(JNIEnv *env, jclass clazz, jlong threadID) {
22+
pthread_t pthread_id = (pthread_t) threadID;
23+
return pthread_kill(pthread_id, SIGVTALRM);
24+
}

src/main/java/org/truffleruby/RubyContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public RubyContext(RubyLanguage language, TruffleLanguage.Env env) {
214214

215215
Metrics.printTime("before-thread-manager");
216216
threadManager = new ThreadManager(this, language);
217-
threadManager.initialize(truffleNFIPlatform, nativeConfiguration);
217+
threadManager.initialize();
218218
threadManager.initializeMainThread(Thread.currentThread());
219219
Metrics.printTime("after-thread-manager");
220220

@@ -300,7 +300,7 @@ protected boolean patch(Env newEnv) {
300300
this.truffleNFIPlatform = createNativePlatform();
301301
encodingManager.initializeDefaultEncodings(truffleNFIPlatform, nativeConfiguration);
302302

303-
threadManager.initialize(truffleNFIPlatform, nativeConfiguration);
303+
threadManager.initialize();
304304
threadManager.restartMainThread(Thread.currentThread());
305305

306306
Metrics.printTime("before-rehash");

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

Lines changed: 11 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,20 @@
3232
import org.truffleruby.core.klass.RubyClass;
3333
import org.truffleruby.core.string.StringUtils;
3434
import org.truffleruby.core.support.PRNGRandomizerNodes;
35-
import org.truffleruby.extra.ffi.Pointer;
3635
import org.truffleruby.language.Nil;
3736
import org.truffleruby.language.SafepointManager;
3837
import org.truffleruby.language.control.DynamicReturnException;
3938
import org.truffleruby.language.control.ExitException;
4039
import org.truffleruby.language.control.KillException;
4140
import org.truffleruby.language.control.RaiseException;
4241
import org.truffleruby.language.objects.shared.SharedObjects;
43-
import org.truffleruby.platform.NativeConfiguration;
44-
import org.truffleruby.platform.TruffleNFIPlatform;
45-
import org.truffleruby.platform.TruffleNFIPlatform.NativeFunction;
4642

4743
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
4844
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
4945
import com.oracle.truffle.api.TruffleStackTrace;
5046
import com.oracle.truffle.api.nodes.Node;
5147
import com.oracle.truffle.api.object.Shape;
48+
import org.truffleruby.signal.LibRubySignal;
5249

5350
public class ThreadManager {
5451

@@ -104,13 +101,10 @@ void restore(UnblockingAction action) {
104101
public static final UnblockingAction EMPTY_UNBLOCKING_ACTION = () -> {
105102
};
106103

104+
private boolean nativeInterrupt;
107105
private final ThreadLocal<UnblockingAction> blockingNativeCallUnblockingAction = ThreadLocal
108106
.withInitial(() -> EMPTY_UNBLOCKING_ACTION);
109107

110-
private int SIGVTALRM;
111-
private NativeFunction pthread_self;
112-
private NativeFunction pthread_kill;
113-
114108
private final ExecutorService fiberPool;
115109

116110
public ThreadManager(RubyContext context, RubyLanguage language) {
@@ -120,10 +114,11 @@ public ThreadManager(RubyContext context, RubyLanguage language) {
120114
this.fiberPool = Executors.newCachedThreadPool(this::createFiberJavaThread);
121115
}
122116

123-
public void initialize(TruffleNFIPlatform nfi, NativeConfiguration nativeConfiguration) {
124-
if (context.getOptions().NATIVE_INTERRUPT && nfi != null) {
125-
setupSignalHandler(nfi, nativeConfiguration);
126-
setupNativeThreadSupport(nfi, nativeConfiguration);
117+
public void initialize() {
118+
nativeInterrupt = context.getOptions().NATIVE_INTERRUPT && context.getRubyHome() != null;
119+
if (nativeInterrupt) {
120+
LibRubySignal.loadLibrary(context.getRubyHome());
121+
LibRubySignal.setupSIGVTALRMEmptySignalHandler();
127122
}
128123
}
129124

@@ -251,37 +246,6 @@ private boolean getGlobalAbortOnException() {
251246
return (boolean) DynamicObjectLibrary.getUncached().getOrDefault(threadClass, "@abort_on_exception", null);
252247
}
253248

254-
private void setupSignalHandler(TruffleNFIPlatform nfi, NativeConfiguration config) {
255-
SIGVTALRM = (int) config.get("platform.signal.SIGVTALRM");
256-
257-
final Object libC = nfi.getDefaultLibrary();
258-
259-
// We use abs() as a function taking a int and having no side effects
260-
final Object abs = nfi.lookup(libC, "abs");
261-
final NativeFunction sigaction = nfi.getFunction("sigaction", "(sint32,pointer,pointer):sint32");
262-
263-
final int sizeOfSigAction = (int) config.get("platform.sigaction.sizeof");
264-
final int handlerOffset = (int) config.get("platform.sigaction.sa_handler.offset");
265-
266-
try (Pointer structSigAction = Pointer.calloc(sizeOfSigAction)) {
267-
structSigAction.writeLong(handlerOffset, nfi.asPointer(abs));
268-
269-
// flags = 0 is OK as we want no SA_RESTART so we can interrupt blocking syscalls.
270-
final int result = (int) sigaction.call(SIGVTALRM, structSigAction.getAddress(), 0L);
271-
if (result != 0) {
272-
// TODO (eregon, 24 Nov. 2017): we should show the NFI errno here.
273-
throw new UnsupportedOperationException("sigaction() failed");
274-
}
275-
}
276-
}
277-
278-
private void setupNativeThreadSupport(TruffleNFIPlatform nfi, NativeConfiguration nativeConfiguration) {
279-
final String pthread_t = nfi.resolveType(nativeConfiguration, "pthread_t");
280-
281-
pthread_self = nfi.getFunction("pthread_self", "():" + pthread_t);
282-
pthread_kill = nfi.getFunction("pthread_kill", "(" + pthread_t + ",sint32):sint32");
283-
}
284-
285249
public void initialize(RubyThread rubyThread, Node currentNode, String info, String sharingReason,
286250
Supplier<Object> task) {
287251
startSharing(rubyThread, sharingReason);
@@ -541,10 +505,9 @@ public void initializeValuesForJavaThread(RubyThread rubyThread, Thread thread)
541505
foreignThreadMap.put(thread, rubyThread);
542506
}
543507

544-
if (pthread_self != null && isRubyManagedThread(thread)) {
545-
final Object pThreadID = pthread_self.call();
546-
547-
blockingNativeCallUnblockingAction.set(() -> pthread_kill.call(pThreadID, SIGVTALRM));
508+
if (nativeInterrupt && isRubyManagedThread(thread)) {
509+
final long threadID = LibRubySignal.threadID();
510+
blockingNativeCallUnblockingAction.set(() -> LibRubySignal.sendSIGVTALRMToThread(threadID));
548511
}
549512

550513
unblockingActions.put(thread, new UnblockingActionHolder(thread, () -> thread.interrupt()));
@@ -556,7 +519,7 @@ public void cleanupValuesForJavaThread(Thread thread) {
556519
}
557520
foreignThreadMap.remove(thread);
558521

559-
if (pthread_self != null) {
522+
if (nativeInterrupt) {
560523
blockingNativeCallUnblockingAction.remove();
561524
}
562525

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.signal;
11+
12+
import org.truffleruby.platform.Platform;
13+
14+
public abstract class LibRubySignal {
15+
16+
public static void loadLibrary(String rubyHome) {
17+
final String path = rubyHome + "/lib/cext/librubysignal" + Platform.LIB_SUFFIX;
18+
System.load(path);
19+
}
20+
21+
public static native int setupSIGVTALRMEmptySignalHandler();
22+
23+
public static native long threadID();
24+
25+
public static native int sendSIGVTALRMToThread(long thread);
26+
27+
}

0 commit comments

Comments
 (0)