Skip to content

Commit 6f0ce72

Browse files
committed
[GR-45825] Support cancellation with Fibers
PullRequest: truffleruby/3805
2 parents 3a4fe79 + 3560b0f commit 6f0ce72

File tree

7 files changed

+52
-28
lines changed

7 files changed

+52
-28
lines changed

ci.jsonnet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ local part_definitions = {
211211
host_inlining_log: {
212212
# Same as in mx.truffleruby/native-host-inlining
213213
mx_options+:: [
214-
"--extra-image-builder-argument=rubyvm:-H:Log=TruffleHostInliningPhase,~CanonicalizerPhase,~GraphBuilderPhase",
214+
"--extra-image-builder-argument=rubyvm:-H:Log=HostInliningPhase,~CanonicalizerPhase,~GraphBuilderPhase",
215215
"--extra-image-builder-argument=rubyvm:-H:+TruffleHostInliningPrintExplored",
216216
"--extra-image-builder-argument=rubyvm:-Dgraal.LogFile=host-inlining.txt",
217217
],

common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"Jsonnet files should not include this file directly but use ci/common.jsonnet instead."
55
],
66

7-
"mx_version": "6.19.3",
7+
"mx_version": "6.20.3",
88

99
"COMMENT.jdks": "When adding or removing JDKs keep in sync with JDKs in ci/common.jsonnet",
1010
"jdks": {

mx.truffleruby/native-host-inlining

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ GRAALVM_SKIP_ARCHIVE=true
22
DYNAMIC_IMPORTS=/tools,/compiler,/substratevm
33
COMPONENTS=TruffleRuby,suite:tools,GraalVM compiler,SubstrateVM,Native Image
44
NATIVE_IMAGES=lib:rubyvm
5-
EXTRA_IMAGE_BUILDER_ARGUMENTS=rubyvm:-H:Log=TruffleHostInliningPhase,~CanonicalizerPhase,~GraphBuilderPhase rubyvm:-H:+TruffleHostInliningPrintExplored rubyvm:-Dgraal.LogFile=host-inlining.txt
5+
EXTRA_IMAGE_BUILDER_ARGUMENTS=rubyvm:-H:Log=HostInliningPhase,~CanonicalizerPhase,~GraphBuilderPhase rubyvm:-H:+TruffleHostInliningPrintExplored rubyvm:-Dgraal.LogFile=host-inlining.txt
66
# To also create the standalone
77
DISABLE_INSTALLABLES=false

mx.truffleruby/suite.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{
88
"name": "regex",
99
"subdir": True,
10-
"version": "db464f45d94a76851b5d1c9e6c2401157c9b9ac5",
10+
"version": "691c375e722825f333ce933e7164fbd7fde8fc44",
1111
"urls": [
1212
{"url": "https://github.com/oracle/graal.git", "kind": "git"},
1313
{"url": "https://curio.ssw.jku.at/nexus/content/repositories/snapshots", "kind": "binary"},
@@ -16,7 +16,7 @@
1616
{
1717
"name": "sulong",
1818
"subdir": True,
19-
"version": "db464f45d94a76851b5d1c9e6c2401157c9b9ac5",
19+
"version": "691c375e722825f333ce933e7164fbd7fde8fc44",
2020
"urls": [
2121
{"url": "https://github.com/oracle/graal.git", "kind": "git"},
2222
{"url": "https://curio.ssw.jku.at/nexus/content/repositories/snapshots", "kind": "binary"},

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import com.oracle.truffle.api.TruffleSafepoint.Interrupter;
1616
import org.truffleruby.core.fiber.RubyFiber.FiberStatus;
1717

18-
import com.oracle.truffle.api.TruffleContext;
1918
import com.oracle.truffle.api.TruffleSafepoint;
2019
import org.truffleruby.RubyContext;
2120
import org.truffleruby.RubyLanguage;
@@ -75,9 +74,8 @@ private void createThreadToReceiveFirstMessage(RubyFiber fiber, Node currentNode
7574
fiber.initializeNode = null;
7675

7776
var sourceSection = block.getSharedMethodInfo().getSourceSection();
78-
final TruffleContext truffleContext = context.getEnv().getContext();
7977

80-
truffleContext.leaveAndEnter(currentNode, Interrupter.THREAD_INTERRUPT, (unused) -> {
78+
context.getThreadManager().leaveAndEnter(currentNode, Interrupter.THREAD_INTERRUPT, (unused) -> {
8179
Thread thread = context.getThreadManager().createFiberJavaThread(fiber, sourceSection,
8280
() -> beforeEnter(fiber, initializeNode),
8381
() -> fiberMain(context, fiber, block, initializeNode),
@@ -158,6 +156,9 @@ private void fiberMain(RubyContext context, RubyFiber fiber, RubyProc block, Nod
158156
} catch (FiberShutdownException e) {
159157
// Ends execution of the Fiber
160158
lastMessage = null;
159+
} catch (ThreadDeath e) { // Context#close(true), handled by Truffle
160+
lastMessage = null;
161+
throw e;
161162
} catch (BreakException e) {
162163
final RubyException exception = context.getCoreExceptions().breakFromProcClosure(currentNode);
163164
lastMessage = new FiberExceptionMessage(new RaiseException(context, exception));
@@ -223,7 +224,7 @@ private void addToMessageQueue(RubyFiber fiber, FiberMessage message) {
223224
@TruffleBoundary
224225
private FiberMessage waitMessage(RubyFiber fiber, Node currentNode) throws InterruptedException {
225226
assertNotEntered("should have left context while waiting fiber message");
226-
return fiber.messageQueue.take();
227+
return Objects.requireNonNull(fiber.messageQueue.take());
227228
}
228229

229230
private void assertNotEntered(String reason) {
@@ -314,8 +315,7 @@ private FiberMessage resumeAndWait(RubyFiber fromFiber, RubyFiber toFiber, Fiber
314315
context.fiberManager.createThreadToReceiveFirstMessage(toFiber, currentNode);
315316
}
316317

317-
final TruffleContext truffleContext = context.getEnv().getContext();
318-
final FiberMessage message = truffleContext.leaveAndEnter(currentNode, Interrupter.THREAD_INTERRUPT,
318+
var message = context.getThreadManager().leaveAndEnter(currentNode, Interrupter.THREAD_INTERRUPT,
319319
(unused) -> {
320320
resume(fromFiber, toFiber, operation, descriptor, args);
321321
return waitMessage(fromFiber, currentNode);
@@ -326,8 +326,7 @@ private FiberMessage resumeAndWait(RubyFiber fromFiber, RubyFiber toFiber, Fiber
326326

327327
@TruffleBoundary
328328
public void safepoint(RubyFiber fromFiber, RubyFiber fiber, SafepointAction action, Node currentNode) {
329-
final TruffleContext truffleContext = context.getEnv().getContext();
330-
final FiberResumeMessage returnMessage = (FiberResumeMessage) truffleContext.leaveAndEnter(currentNode,
329+
var returnMessage = (FiberResumeMessage) context.getThreadManager().leaveAndEnter(currentNode,
331330
Interrupter.THREAD_INTERRUPT, (unused) -> {
332331
addToMessageQueue(fiber, new FiberSafepointMessage(fromFiber, action));
333332
return waitMessage(fromFiber, currentNode);
@@ -382,8 +381,7 @@ public void killOtherFibers(RubyThread thread) {
382381
final TruffleSafepoint safepoint = TruffleSafepoint.getCurrent();
383382
boolean allowSideEffects = safepoint.setAllowSideEffects(false);
384383
try {
385-
final TruffleContext truffleContext = context.getEnv().getContext();
386-
truffleContext.leaveAndEnter(DummyNode.INSTANCE, Interrupter.THREAD_INTERRUPT, (unused) -> {
384+
context.getThreadManager().leaveAndEnter(DummyNode.INSTANCE, Interrupter.THREAD_INTERRUPT, (unused) -> {
387385
doKillOtherFibers(thread);
388386
return BlockingAction.SUCCESS;
389387
}, null);

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.lang.reflect.InvocationTargetException;
1313
import java.lang.reflect.Method;
14+
import java.util.Objects;
1415
import java.util.Set;
1516
import java.util.Timer;
1617
import java.util.concurrent.ConcurrentHashMap;
@@ -23,6 +24,7 @@
2324
import com.oracle.truffle.api.CompilerAsserts;
2425
import com.oracle.truffle.api.CompilerDirectives;
2526
import com.oracle.truffle.api.CompilerDirectives.ValueType;
27+
import com.oracle.truffle.api.TruffleContext;
2628
import com.oracle.truffle.api.TruffleOptions;
2729
import com.oracle.truffle.api.TruffleSafepoint;
2830
import com.oracle.truffle.api.TruffleSafepoint.Interrupter;
@@ -607,6 +609,15 @@ public <T> T runUntilResult(Node currentNode, BlockingAction<T> action, Runnable
607609
}
608610
}
609611

612+
/** Same as {@link TruffleContext#leaveAndEnter} but ensures it never returns null. Also the interruptible must
613+
* never return null. */
614+
public <T, R> R leaveAndEnter(Node node, Interrupter interrupter, InterruptibleFunction<T, R> interruptible,
615+
T object) {
616+
final TruffleContext truffleContext = context.getEnv().getContext();
617+
R result = truffleContext.leaveAndEnter(node, interrupter, interruptible, object);
618+
return Objects.requireNonNull(result);
619+
}
620+
610621
@TruffleBoundary
611622
Interrupter getNativeCallInterrupter() {
612623
if (nativeInterrupt) {

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

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
import static org.junit.Assert.assertTrue;
1616

1717
import org.graalvm.polyglot.Context;
18-
import org.graalvm.polyglot.PolyglotException;
1918
import org.graalvm.polyglot.Value;
20-
import org.junit.Assert;
2119
import org.junit.Test;
2220

2321
public class MiscTest {
@@ -49,26 +47,43 @@ public void timeoutExecution() throws Throwable {
4947
TestingThread thread = new TestingThread(() -> {
5048
try {
5149
Thread.sleep(1000);
52-
context.close(true);
5350
} catch (InterruptedException e) {
5451
throw new Error(e);
55-
} catch (PolyglotException e) {
56-
if (e.isCancelled()) {
57-
assertTrue(e.isCancelled());
58-
} else {
59-
throw e;
60-
}
6152
}
53+
context.close(true);
6254
});
6355

6456
context.eval("ruby", "init = 1");
6557
thread.start();
6658
try {
6759
String maliciousCode = "while true; end";
68-
context.eval("ruby", maliciousCode);
69-
Assert.fail();
70-
} catch (PolyglotException e) {
71-
assertTrue(e.isCancelled());
60+
RubyTest.assertThrows(() -> context.eval("ruby", maliciousCode),
61+
e -> assertTrue(e.isCancelled()));
62+
} finally {
63+
thread.join();
64+
}
65+
}
66+
67+
@Test
68+
public void testCancellationWithFibers() throws Throwable {
69+
Context context = RubyTest.createContext();
70+
71+
// schedule a timeout in 100ms
72+
TestingThread thread = new TestingThread(() -> {
73+
try {
74+
Thread.sleep(100);
75+
} catch (InterruptedException e) {
76+
throw new Error(e);
77+
}
78+
context.close(true);
79+
});
80+
81+
context.eval("ruby", "init = 1");
82+
thread.start();
83+
try {
84+
String code = "unstarted = Fiber.new {}; resumed = Fiber.new { Fiber.yield }.tap(&:resume); sleep 1";
85+
RubyTest.assertThrows(() -> context.eval("ruby", code),
86+
e -> assertTrue(e.isCancelled()));
7287
} finally {
7388
thread.join();
7489
}

0 commit comments

Comments
 (0)