Skip to content

Commit 5ccc678

Browse files
committed
[Truffle] Rewrite a good part of RubyFiber and RubyThread.
* Now a Thread has multipe Fibers and always has a root Fiber. * Properly kill Fibers when a thread exits. * Share all the logic.
1 parent d5f5320 commit 5ccc678

File tree

10 files changed

+155
-89
lines changed

10 files changed

+155
-89
lines changed

truffle/src/main/java/org/jruby/truffle/nodes/core/FiberNodes.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.jruby.truffle.runtime.core.RubyFiber;
2424
import org.jruby.truffle.runtime.core.RubyNilClass;
2525
import org.jruby.truffle.runtime.core.RubyProc;
26+
import org.jruby.truffle.runtime.core.RubyThread;
2627

2728
@CoreClass(name = "Fiber")
2829
public abstract class FiberNodes {
@@ -57,12 +58,13 @@ protected Object transfer(RubyFiber fiber, boolean isYield, Object[] args) {
5758
throw new RaiseException(getContext().getCoreLibrary().deadFiberCalledError(this));
5859
}
5960

60-
if (fiber.getRubyThread() != getContext().getThreadManager().getCurrentThread()) {
61+
RubyThread currentThread = getContext().getThreadManager().getCurrentThread();
62+
if (fiber.getRubyThread() != currentThread) {
6163
CompilerDirectives.transferToInterpreter();
6264
throw new RaiseException(getContext().getCoreLibrary().fiberError("fiber called across threads", this));
6365
}
6466

65-
final RubyFiber sendingFiber = getContext().getFiberManager().getCurrentFiber();
67+
final RubyFiber sendingFiber = currentThread.getFiberManager().getCurrentFiber();
6668

6769
return singleValue(sendingFiber.transferControlTo(fiber, isYield, args));
6870
}
@@ -129,10 +131,11 @@ public YieldNode(YieldNode prev) {
129131

130132
@Specialization
131133
public Object yield(Object[] args) {
132-
final RubyFiber yieldingFiber = getContext().getFiberManager().getCurrentFiber();
134+
RubyThread currentThread = getContext().getThreadManager().getCurrentThread();
135+
final RubyFiber yieldingFiber = currentThread.getFiberManager().getCurrentFiber();
133136
final RubyFiber fiberYieldedTo = yieldingFiber.getLastResumedByFiber();
134137

135-
if (yieldingFiber.isTopLevel() || fiberYieldedTo == null) {
138+
if (yieldingFiber.isRootFiber() || fiberYieldedTo == null) {
136139
throw new RaiseException(getContext().getCoreLibrary().yieldFromRootFiberError(this));
137140
}
138141

truffle/src/main/java/org/jruby/truffle/nodes/core/ThreadNodes.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public ExitModuleNode(ExitModuleNode prev) {
8080

8181
@Specialization
8282
public RubyNilClass exit() {
83-
getContext().getThreadManager().getCurrentThread().exit();
83+
getContext().getThreadManager().getCurrentThread().shutdown();
8484
return nil();
8585
}
8686

@@ -100,14 +100,12 @@ public KillNode(KillNode prev) {
100100
@Specialization
101101
public RubyThread kill(final RubyThread thread) {
102102
getContext().getSafepointManager().pauseAllThreadsAndExecute(this, new SafepointAction() {
103-
104103
@Override
105104
public void run(RubyThread currentThread, Node currentNode) {
106-
if (currentThread == thread) {
107-
currentThread.exit();
105+
if (currentThread == thread && thread.isCurrentJavaThreadRootFiber()) {
106+
thread.shutdown();
108107
}
109108
}
110-
111109
});
112110

113111
return thread;
@@ -266,7 +264,7 @@ public RubyNilClass raise(VirtualFrame frame, final RubyThread thread, RubyClass
266264

267265
@Override
268266
public void run(RubyThread currentThread, Node currentNode) {
269-
if (currentThread == thread) {
267+
if (currentThread == thread && thread.isCurrentJavaThreadCurrentFiber()) {
270268
throw exceptionWrapper;
271269
}
272270
}

truffle/src/main/java/org/jruby/truffle/runtime/RubyContext.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import jnr.posix.POSIX;
2222
import jnr.posix.POSIXFactory;
23+
2324
import org.jcodings.Encoding;
2425
import org.jcodings.specific.ASCIIEncoding;
2526
import org.jcodings.specific.UTF8Encoding;
@@ -64,7 +65,6 @@ public class RubyContext extends ExecutionContext {
6465
private final TraceManager traceManager;
6566
private final ObjectSpaceManager objectSpaceManager;
6667
private final ThreadManager threadManager;
67-
private final FiberManager fiberManager;
6868
private final AtExitManager atExitManager;
6969
private final RubySymbol.SymbolTable symbolTable = new RubySymbol.SymbolTable(this);
7070
private final Shape emptyShape;
@@ -118,10 +118,8 @@ public RubyContext(Ruby runtime) {
118118
traceManager = new TraceManager();
119119
atExitManager = new AtExitManager();
120120

121-
// Must initialize threads before fibers
122-
123121
threadManager = new ThreadManager(this);
124-
fiberManager = new FiberManager(this);
122+
threadManager.initialize();
125123

126124
rubiniusPrimitiveManager = RubiniusPrimitiveManager.create();
127125

@@ -280,8 +278,6 @@ public void shutdown() {
280278
instrumentationServerManager.shutdown();
281279
}
282280

283-
fiberManager.shutdown();
284-
285281
threadManager.shutdown();
286282
}
287283

@@ -469,10 +465,6 @@ public ObjectSpaceManager getObjectSpaceManager() {
469465
return objectSpaceManager;
470466
}
471467

472-
public FiberManager getFiberManager() {
473-
return fiberManager;
474-
}
475-
476468
public ThreadManager getThreadManager() {
477469
return threadManager;
478470
}

truffle/src/main/java/org/jruby/truffle/runtime/core/RubyFiber.java

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
import java.util.concurrent.ArrayBlockingQueue;
2727
import java.util.concurrent.BlockingQueue;
28+
import java.util.concurrent.Callable;
29+
import java.util.concurrent.LinkedBlockingQueue;
2830

2931
/**
3032
* Represents the Ruby {@code Fiber} class. The current implementation uses Java threads and message
@@ -80,67 +82,100 @@ public RubyException getException() {
8082
}
8183

8284
public static class FiberExitException extends ControlFlowException {
83-
8485
private static final long serialVersionUID = 1522270454305076317L;
85-
8686
}
8787

8888
private final FiberManager fiberManager;
8989
private final ThreadManager threadManager;
9090
private final RubyThread rubyThread;
9191

9292
private String name;
93-
private final boolean topLevel;
94-
private final BlockingQueue<FiberMessage> messageQueue = new ArrayBlockingQueue<>(1);
93+
private final boolean isRootFiber;
94+
// we need 2 slots when the safepoint manager sends the kill message and there is another message unprocessed
95+
private final BlockingQueue<FiberMessage> messageQueue = new LinkedBlockingQueue<>(2);
9596
private RubyFiber lastResumedByFiber = null;
9697
private boolean alive = true;
9798

98-
public RubyFiber(RubyClass rubyClass, FiberManager fiberManager, ThreadManager threadManager, String name, boolean topLevel) {
99+
protected volatile Thread thread;
100+
101+
public RubyFiber(RubyThread parent, RubyClass rubyClass, String name) {
102+
this(parent, parent.getFiberManager(), parent.getThreadManager(), rubyClass, name, false);
103+
}
104+
105+
public static RubyFiber newRootFiber(RubyThread thread, FiberManager fiberManager, ThreadManager threadManager) {
106+
RubyContext context = thread.getContext();
107+
return new RubyFiber(thread, fiberManager, threadManager, context.getCoreLibrary().getFiberClass(), "root Fiber for Thread", true);
108+
}
109+
110+
private RubyFiber(RubyThread parent, FiberManager fiberManager, ThreadManager threadManager, RubyClass rubyClass, String name, boolean isRootFiber) {
99111
super(rubyClass);
112+
this.rubyThread = parent;
100113
this.fiberManager = fiberManager;
101114
this.threadManager = threadManager;
102115
this.name = name;
103-
this.topLevel = topLevel;
104-
this.rubyThread = threadManager.getCurrentThread();
116+
this.isRootFiber = isRootFiber;
105117
}
106118

107-
public void initialize(RubyProc block) {
119+
public void initialize(final RubyProc block) {
108120
RubyNode.notDesignedForCompilation();
109121

110122
name = "Ruby Fiber@" + block.getSharedMethodInfo().getSourceSection().getShortDescription();
111-
112-
final RubyFiber finalFiber = this;
113-
final RubyProc finalBlock = block;
114-
115123
final Thread thread = new Thread(new Runnable() {
116-
117124
@Override
118125
public void run() {
119-
fiberManager.registerFiber(finalFiber);
120-
finalFiber.getContext().getSafepointManager().enterThread();
121-
threadManager.enterGlobalLock(rubyThread);
126+
handleFiberExceptions(block);
127+
}
128+
});
129+
thread.setName(name);
130+
thread.start();
131+
}
122132

133+
private void handleFiberExceptions(final RubyProc block) {
134+
run(new Runnable() {
135+
@Override
136+
public void run() {
123137
try {
124-
final Object[] args = finalFiber.waitForResume();
125-
final Object result = finalBlock.rootCall(args);
126-
finalFiber.resume(finalFiber.lastResumedByFiber, true, result);
127-
} catch (FiberExitException | ThreadExitException e) { // TODO (eregon, 21 Apr. 2015): The thread should cleanly kill its fibers when dying.
128-
// Naturally exit the thread on catching this
138+
final Object[] args = waitForResume();
139+
final Object result = block.rootCall(args);
140+
resume(lastResumedByFiber, true, result);
141+
} catch (FiberExitException e) {
142+
assert !isRootFiber;
143+
// Naturally exit the Java thread on catching this
129144
} catch (ReturnException e) {
130-
sendMessageTo(finalFiber.lastResumedByFiber, new FiberExceptionMessage(finalFiber.getContext().getCoreLibrary().unexpectedReturn(null)));
145+
sendMessageTo(lastResumedByFiber, new FiberExceptionMessage(getContext().getCoreLibrary().unexpectedReturn(null)));
131146
} catch (RaiseException e) {
132-
sendMessageTo(finalFiber.lastResumedByFiber, new FiberExceptionMessage(e.getRubyException()));
133-
} finally {
134-
alive = false;
135-
threadManager.leaveGlobalLock();
136-
finalFiber.getContext().getSafepointManager().leaveThread();
137-
fiberManager.unregisterFiber(finalFiber);
147+
sendMessageTo(lastResumedByFiber, new FiberExceptionMessage(e.getRubyException()));
138148
}
139149
}
140-
141150
});
142-
thread.setName(name);
143-
thread.start();
151+
}
152+
153+
protected void run(final Runnable task) {
154+
RubyNode.notDesignedForCompilation();
155+
156+
start();
157+
try {
158+
task.run();
159+
} finally {
160+
cleanup();
161+
}
162+
}
163+
164+
// Only used by the main thread which cannot easily wrap everything inside a try/finally.
165+
public void start() {
166+
thread = Thread.currentThread();
167+
fiberManager.registerFiber(this);
168+
getContext().getSafepointManager().enterThread();
169+
threadManager.enterGlobalLock(rubyThread);
170+
}
171+
172+
// Only used by the main thread which cannot easily wrap everything inside a try/finally.
173+
public void cleanup() {
174+
alive = false;
175+
threadManager.leaveGlobalLock();
176+
getContext().getSafepointManager().leaveThread();
177+
fiberManager.unregisterFiber(this);
178+
thread = null;
144179
}
145180

146181
public RubyThread getRubyThread() {
@@ -201,6 +236,7 @@ public Object[] transferControlTo(RubyFiber fiber, boolean yield, Object[] args)
201236
}
202237

203238
public void shutdown() {
239+
assert !isRootFiber;
204240
RubyNode.notDesignedForCompilation();
205241

206242
sendMessageTo(this, new FiberExitMessage());
@@ -216,8 +252,8 @@ public RubyFiber getLastResumedByFiber() {
216252
return lastResumedByFiber;
217253
}
218254

219-
public boolean isTopLevel() {
220-
return topLevel;
255+
public boolean isRootFiber() {
256+
return isRootFiber;
221257
}
222258

223259
public String getName() {
@@ -228,7 +264,8 @@ public static class FiberAllocator implements Allocator {
228264

229265
@Override
230266
public RubyBasicObject allocate(RubyContext context, RubyClass rubyClass, Node currentNode) {
231-
return new RubyFiber(rubyClass, context.getFiberManager(), context.getThreadManager(), null, false);
267+
RubyThread parent = context.getThreadManager().getCurrentThread();
268+
return new RubyFiber(parent, rubyClass, null);
232269
}
233270

234271
}

0 commit comments

Comments
 (0)