Skip to content

Commit c04323c

Browse files
committed
[GR-22447] Support closing the Context on other threads than the main Ruby Thread
PullRequest: truffleruby/2687
2 parents 6041bea + f92e093 commit c04323c

File tree

5 files changed

+30
-45
lines changed

5 files changed

+30
-45
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,6 @@ private void dispose() {
512512
consoleHolder.close();
513513
}
514514

515-
threadManager.cleanupMainThread();
516515
threadManager.dispose();
517516
threadManager.checkNoRunningThreads();
518517

src/main/java/org/truffleruby/RubyLanguage.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,8 @@ protected boolean isThreadAccessAllowed(Thread thread, boolean singleThreaded) {
521521

522522
@Override
523523
public void initializeThread(RubyContext context, Thread thread) {
524+
LOGGER.fine(() -> "initializeThread(#" + thread.getId() + " " + thread + ")");
525+
524526
if (thread == context.getThreadManager().getOrInitializeRootJavaThread()) {
525527
// Already initialized when creating the context
526528
return;
@@ -546,14 +548,24 @@ public void initializeThread(RubyContext context, Thread thread) {
546548

547549
@Override
548550
public void disposeThread(RubyContext context, Thread thread) {
551+
LOGGER.fine(
552+
() -> "disposeThread(#" + thread.getId() + " " + thread + " " +
553+
context.getThreadManager().getCurrentThreadOrNull() + ")");
554+
549555
if (thread == context.getThreadManager().getRootJavaThread()) {
550556
if (context.getEnv().isPreInitialization()) {
551557
// Cannot save the root Java Thread instance in the image
552558
context.getThreadManager().resetMainThread();
553559
context.getThreadManager().dispose();
560+
return;
561+
} else if (!context.isInitialized()) {
562+
// Context patching failed, we cannot cleanup the main thread as it was not initialized
563+
return;
564+
} else {
565+
// Cleanup the main thread, this is done between finalizeContext() and disposeContext()
566+
context.getThreadManager().cleanupThreadState(context.getThreadManager().getRootThread(), thread);
567+
return;
554568
}
555-
// Let the context shutdown cleanup the main thread
556-
return;
557569
}
558570

559571
if (context.getThreadManager().isRubyManagedThread(thread)) {
@@ -563,12 +575,13 @@ public void disposeThread(RubyContext context, Thread thread) {
563575
throw CompilerDirectives.shouldNotReachHere("Ruby threads should be disposed on their Java thread");
564576
}
565577
context.getThreadManager().cleanupThreadState(rubyThread, thread);
566-
} else {
567-
// Fiber
578+
} else { // (non-root) Fiber
579+
// Fibers are always cleaned up by their thread's cleanup with FiberManager#killOtherFibers()
568580
}
569581
return;
570582
}
571583

584+
// A foreign Thread, its Fibers are considered isRubyManagedThread()
572585
final RubyThread rubyThread = context.getThreadManager().getRubyThread(thread);
573586
context.getThreadManager().cleanup(rubyThread, thread);
574587
}

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -397,14 +397,12 @@ public String getFiberDebugInfo() {
397397
for (RubyFiber fiber : runningFibers) {
398398
builder.append(" fiber @");
399399
builder.append(ObjectIDNode.getUncached().execute(fiber));
400-
builder.append(" #");
401400

402401
final Thread thread = fiber.thread;
403-
404402
if (thread == null) {
405-
builder.append("(no Java thread)");
403+
builder.append(" (no Java thread)");
406404
} else {
407-
builder.append(thread.getId());
405+
builder.append(" #").append(thread.getId()).append(' ').append(thread);
408406
}
409407

410408
if (fiber == rootFiber) {

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

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -677,39 +677,21 @@ public void checkNoRunningThreads() {
677677
}
678678
}
679679

680-
private void checkCalledInMainThreadRootFiber() {
681-
final RubyThread currentThread = getCurrentThread();
682-
if (currentThread != rootThread) {
683-
throw new UnsupportedOperationException(StringUtils.format(
684-
"ThreadManager.shutdown() must be called on the root Ruby Thread (%s) but was called on %s",
685-
rootThread,
686-
currentThread));
687-
}
688-
689-
final FiberManager fiberManager = rootThread.fiberManager;
690-
691-
if (getRubyFiberFromCurrentJavaThread() != fiberManager.getRootFiber()) {
692-
throw new UnsupportedOperationException(
693-
"ThreadManager.shutdown() must be called on the root Fiber of the main Thread");
694-
}
695-
}
696-
697680
@TruffleBoundary
698681
public void killAndWaitOtherThreads() {
699-
checkCalledInMainThreadRootFiber();
700-
701682
// Disallow new Fibers to be created
702683
fiberPool.shutdown();
703684

704685
// Kill all Ruby Threads and Fibers
705686

706687
// The logic below avoids using the SafepointManager if there is
707-
// only the root thread and the reference processing thread.
688+
// only the current thread and the reference processing thread.
689+
final RubyThread currentThread = getCurrentThread();
708690
boolean otherThreads = false;
709691
RubyThread referenceProcessingThread = null;
710692
for (RubyThread thread : runningRubyThreads) {
711-
if (thread == rootThread) {
712-
// clean up later in #cleanupMainThread
693+
if (thread == currentThread) {
694+
// no need to kill the current thread
713695
} else if (thread == context.getReferenceProcessor().getProcessingThread()) {
714696
referenceProcessingThread = thread;
715697
} else {
@@ -727,7 +709,7 @@ public void killAndWaitOtherThreads() {
727709
if (otherThreads) {
728710
doKillOtherThreads();
729711
}
730-
rootThread.fiberManager.killOtherFibers();
712+
currentThread.fiberManager.killOtherFibers();
731713

732714
// Wait and join all Java threads we created
733715
for (Thread thread : rubyManagedThreads) {
@@ -755,12 +737,6 @@ public void run(RubyThread rubyThread, Node currentNode) {
755737
});
756738
}
757739

758-
@TruffleBoundary
759-
public void cleanupMainThread() {
760-
checkCalledInMainThreadRootFiber();
761-
cleanup(rootThread, rootJavaThread);
762-
}
763-
764740
@TruffleBoundary
765741
public Object[] getThreadList() {
766742
// This must not pre-allocate an array, or it could contain null's.
@@ -784,7 +760,7 @@ public String getThreadDebugInfo() {
784760
}
785761

786762
// cannot use getCurrentThread() as it might have been cleared
787-
if (thread == currentThread.get()) {
763+
if (thread == getCurrentThreadOrNull()) {
788764
builder.append(" (current)");
789765
}
790766

src/test/java/org/truffleruby/MiscTest.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import org.graalvm.polyglot.PolyglotException;
1919
import org.graalvm.polyglot.Value;
2020
import org.junit.Assert;
21-
import org.junit.Ignore;
2221
import org.junit.Test;
2322

2423
public class MiscTest {
@@ -112,9 +111,9 @@ public void testIntegratorThreadRubyThreadInitialization() throws Throwable {
112111
TestingThread thread = new TestingThread(() -> {
113112
// Run code that requires the Ruby Thread object to be initialized
114113
Value recursiveArray = context.eval("ruby", "a = [0]; a[0] = a; a.inspect"); // Access @recursive_objects
115-
Assert.assertEquals("[[...]]", recursiveArray.asString());
116-
Assert.assertTrue(context.eval("ruby", "Thread.current.thread_variable_get('foo')").isNull());
117-
Assert.assertTrue(context.eval("ruby", "rand").fitsInDouble());
114+
assertEquals("[[...]]", recursiveArray.asString());
115+
assertTrue(context.eval("ruby", "Thread.current.thread_variable_get('foo')").isNull());
116+
assertTrue(context.eval("ruby", "rand").fitsInDouble());
118117
});
119118
thread.start();
120119
thread.join();
@@ -155,12 +154,12 @@ public void testForeignThread() throws Throwable {
155154
}
156155
}
157156

158-
@Ignore // TODO (eregon, 8 April 2020): not yet working
159157
@Test
160158
public void testIntegratorThreadContextClosedOnOtherThread() throws Throwable {
161159
try (Context context = Context.newBuilder("ruby").allowCreateThread(true).build()) {
162160
TestingThread thread = new TestingThread(() -> {
163-
Assert.assertEquals(42, context.eval("ruby", "6 * 7").asInt());
161+
assertEquals(context.eval("ruby", "Thread.current"), context.eval("ruby", "Thread.main"));
162+
assertEquals(42, context.eval("ruby", "6 * 7").asInt());
164163
});
165164
thread.start();
166165
thread.join();

0 commit comments

Comments
 (0)