9
9
*/
10
10
package org .truffleruby .core .fiber ;
11
11
12
+ import java .util .Objects ;
12
13
import java .util .concurrent .CountDownLatch ;
13
14
15
+ import com .oracle .truffle .api .TruffleSafepoint .Interrupter ;
14
16
import org .truffleruby .core .fiber .RubyFiber .FiberStatus ;
15
17
16
18
import com .oracle .truffle .api .TruffleContext ;
@@ -60,27 +62,56 @@ public FiberManager(RubyLanguage language, RubyContext context) {
60
62
public void initialize (RubyFiber fiber , boolean blocking , RubyProc block , Node currentNode ) {
61
63
final SourceSection sourceSection = block .getSharedMethodInfo ().getSourceSection ();
62
64
fiber .sourceLocation = context .fileLine (sourceSection );
65
+ fiber .body = block ;
66
+ fiber .initializeNode = currentNode ;
63
67
fiber .blocking = blocking ;
68
+ }
69
+
70
+ private void createThreadToReceiveFirstMessage (RubyFiber fiber , Node currentNode ) {
71
+ assert fiber .thread == null ;
72
+ RubyProc block = Objects .requireNonNull (fiber .body );
73
+ fiber .body = null ;
74
+ Node initializeNode = Objects .requireNonNull (fiber .initializeNode );
75
+ fiber .initializeNode = null ;
64
76
77
+ var sourceSection = block .getSharedMethodInfo ().getSourceSection ();
65
78
final TruffleContext truffleContext = context .getEnv ().getContext ();
66
79
67
- context .getThreadManager ().leaveAndEnter (truffleContext , currentNode , () -> {
68
- context .getThreadManager ().spawnFiber (fiber , sourceSection ,
69
- () -> fiberMain (context , fiber , block , currentNode ));
70
- waitForInitialization (context , fiber , currentNode );
80
+ truffleContext .leaveAndEnter (currentNode , Interrupter .THREAD_INTERRUPT , (unused ) -> {
81
+ Thread thread = context .getThreadManager ().createFiberJavaThread (fiber , sourceSection ,
82
+ () -> beforeEnter (fiber , initializeNode ),
83
+ () -> fiberMain (context , fiber , block , initializeNode ),
84
+ () -> afterLeave (fiber ), currentNode );
85
+ fiber .thread = thread ;
86
+ thread .start ();
87
+ waitForInitializationUnentered (context , fiber , currentNode );
71
88
return BlockingAction .SUCCESS ;
72
- });
89
+ }, null );
73
90
}
74
91
75
92
/** Wait for full initialization of the new fiber */
76
- public static void waitForInitialization (RubyContext context , RubyFiber fiber , Node currentNode ) {
93
+ public static void waitForInitializationEntered (RubyContext context , RubyFiber fiber , Node currentNode ) {
94
+ assert context .getEnv ().getContext ().isEntered ();
77
95
final CountDownLatch initializedLatch = fiber .initializedLatch ;
78
96
79
- if (context .getEnv ().getContext ().isEntered ()) {
80
- context .getThreadManager ().runUntilResultKeepStatus (currentNode , CountDownLatch ::await , initializedLatch );
81
- } else {
82
- context .getThreadManager ().retryWhileInterrupted (currentNode , CountDownLatch ::await , initializedLatch );
97
+ context .getThreadManager ().runUntilResultKeepStatus (currentNode , latch -> {
98
+ latch .await ();
99
+ return BlockingAction .SUCCESS ;
100
+ }, initializedLatch );
101
+
102
+ final Throwable uncaughtException = fiber .uncaughtException ;
103
+ if (uncaughtException != null ) {
104
+ ExceptionOperations .rethrow (uncaughtException );
83
105
}
106
+ }
107
+
108
+ /** Wait for full initialization of the new fiber */
109
+ public static void waitForInitializationUnentered (RubyContext context , RubyFiber fiber , Node currentNode )
110
+ throws InterruptedException {
111
+ assert !context .getEnv ().getContext ().isEntered ();
112
+ final CountDownLatch initializedLatch = fiber .initializedLatch ;
113
+
114
+ initializedLatch .await ();
84
115
85
116
final Throwable uncaughtException = fiber .uncaughtException ;
86
117
if (uncaughtException != null ) {
@@ -90,23 +121,26 @@ public static void waitForInitialization(RubyContext context, RubyFiber fiber, N
90
121
91
122
private static final BranchProfile UNPROFILED = BranchProfile .getUncached ();
92
123
93
- private void fiberMain ( RubyContext context , RubyFiber fiber , RubyProc block , Node currentNode ) {
124
+ private void beforeEnter ( RubyFiber fiber , Node currentNode ) {
94
125
assert !fiber .isRootFiber () : "Root Fibers execute threadMain() and not fiberMain()" ;
95
126
assertNotEntered ("Fibers should start unentered to avoid triggering multithreading" );
96
127
97
128
final Thread thread = Thread .currentThread ();
98
- final TruffleContext truffleContext = context .getEnv ().getContext ();
99
-
100
129
start (fiber , thread );
101
130
102
131
// fully initialized
103
132
fiber .initializedLatch .countDown ();
104
133
105
- final FiberMessage message = waitMessage (fiber , currentNode );
106
- fiber .rubyThread .setCurrentFiber (fiber );
134
+ try {
135
+ fiber .firstMessage = waitMessage (fiber , currentNode );
136
+ } catch (InterruptedException e ) {
137
+ throw CompilerDirectives .shouldNotReachHere ("unexpected interrupt in Fiber beforeEnter()" );
138
+ }
139
+ }
107
140
108
- // enter() polls so we need the current Fiber to be set before enter()
109
- final Object prev = truffleContext .enter (currentNode );
141
+ private void fiberMain (RubyContext context , RubyFiber fiber , RubyProc block , Node currentNode ) {
142
+ final FiberMessage message = Objects .requireNonNull (fiber .firstMessage );
143
+ fiber .firstMessage = null ;
110
144
111
145
FiberMessage lastMessage = null ;
112
146
try {
@@ -134,18 +168,21 @@ private void fiberMain(RubyContext context, RubyFiber fiber, RubyProc block, Nod
134
168
final RuntimeException exception = ThreadManager .printInternalError (e );
135
169
lastMessage = new FiberExceptionMessage (exception );
136
170
} finally {
137
- final RubyFiber returnFiber = lastMessage == null ? null : getReturnFiber (fiber , currentNode , UNPROFILED );
171
+ fiber .lastMessage = lastMessage ;
172
+ fiber .returnFiber = lastMessage == null ? null : getReturnFiber (fiber , currentNode , UNPROFILED );
138
173
139
174
// Perform all cleanup before resuming the parent Fiber
140
175
// Make sure that other fibers notice we are dead before they gain control back
141
176
fiber .status = FiberStatus .TERMINATED ;
142
177
// Leave context before addToMessageQueue() -> parent Fiber starts executing
143
- truffleContext . leave ( currentNode , prev );
144
- cleanup ( fiber , thread );
178
+ }
179
+ }
145
180
146
- if (lastMessage != null ) {
147
- addToMessageQueue (returnFiber , lastMessage );
148
- }
181
+ private void afterLeave (RubyFiber fiber ) {
182
+ if (fiber .lastMessage != null ) {
183
+ addToMessageQueue (fiber .returnFiber , fiber .lastMessage );
184
+ fiber .returnFiber = null ;
185
+ fiber .lastMessage = null ;
149
186
}
150
187
}
151
188
@@ -161,7 +198,7 @@ public RubyFiber getReturnFiber(RubyFiber currentFiber, Node currentNode, Branch
161
198
return previousFiber ;
162
199
} else {
163
200
164
- if (currentFiber == rootFiber ) {
201
+ if (currentFiber == rootFiber ) { // Note: this is always false for the fiberMain() caller
165
202
errorProfile .enter ();
166
203
throw new RaiseException (context , context .getCoreExceptions ().yieldFromRootFiberError (currentNode ));
167
204
}
@@ -184,24 +221,9 @@ private void addToMessageQueue(RubyFiber fiber, FiberMessage message) {
184
221
185
222
/** Send the Java thread that represents this fiber to sleep until it receives a message. */
186
223
@ TruffleBoundary
187
- private FiberMessage waitMessage (RubyFiber fiber , Node currentNode ) {
224
+ private FiberMessage waitMessage (RubyFiber fiber , Node currentNode ) throws InterruptedException {
188
225
assertNotEntered ("should have left context while waiting fiber message" );
189
-
190
- class State {
191
- final RubyFiber fiber ;
192
- FiberMessage message ;
193
-
194
- State (RubyFiber fiber ) {
195
- this .fiber = fiber ;
196
- }
197
- }
198
-
199
- final State state = new State (fiber );
200
- context .getThreadManager ().retryWhileInterrupted (
201
- currentNode ,
202
- s -> s .message = s .fiber .messageQueue .take (),
203
- state );
204
- return state .message ;
226
+ return fiber .messageQueue .take ();
205
227
}
206
228
207
229
private void assertNotEntered (String reason ) {
@@ -287,26 +309,29 @@ public DescriptorAndArgs transferControlTo(RubyFiber fromFiber, RubyFiber toFibe
287
309
@ TruffleBoundary
288
310
private FiberMessage resumeAndWait (RubyFiber fromFiber , RubyFiber toFiber , FiberOperation operation ,
289
311
ArgumentsDescriptor descriptor , Object [] args , Node currentNode ) {
312
+
313
+ if (toFiber .body != null ) {
314
+ context .fiberManager .createThreadToReceiveFirstMessage (toFiber , currentNode );
315
+ }
316
+
290
317
final TruffleContext truffleContext = context .getEnv ().getContext ();
291
- final FiberMessage message = context
292
- .getThreadManager ()
293
- .leaveAndEnter (truffleContext , currentNode , () -> {
318
+ final FiberMessage message = truffleContext .leaveAndEnter (currentNode , Interrupter .THREAD_INTERRUPT ,
319
+ (unused ) -> {
294
320
resume (fromFiber , toFiber , operation , descriptor , args );
295
321
return waitMessage (fromFiber , currentNode );
296
- });
322
+ }, null );
297
323
fromFiber .rubyThread .setCurrentFiber (fromFiber );
298
324
return message ;
299
325
}
300
326
301
327
@ TruffleBoundary
302
328
public void safepoint (RubyFiber fromFiber , RubyFiber fiber , SafepointAction action , Node currentNode ) {
303
329
final TruffleContext truffleContext = context .getEnv ().getContext ();
304
- final FiberResumeMessage returnMessage = (FiberResumeMessage ) context
305
- .getThreadManager ()
306
- .leaveAndEnter (truffleContext , currentNode , () -> {
330
+ final FiberResumeMessage returnMessage = (FiberResumeMessage ) truffleContext .leaveAndEnter (currentNode ,
331
+ Interrupter .THREAD_INTERRUPT , (unused ) -> {
307
332
addToMessageQueue (fiber , new FiberSafepointMessage (fromFiber , action ));
308
333
return waitMessage (fromFiber , currentNode );
309
- });
334
+ }, null );
310
335
fromFiber .rubyThread .setCurrentFiber (fromFiber );
311
336
312
337
if (returnMessage .getArgs () != SAFEPOINT_ARGS ) {
@@ -315,7 +340,11 @@ public void safepoint(RubyFiber fromFiber, RubyFiber fiber, SafepointAction acti
315
340
}
316
341
317
342
public void start (RubyFiber fiber , Thread javaThread ) {
318
- fiber .thread = javaThread ;
343
+ if (fiber .isRootFiber ()) {
344
+ fiber .thread = javaThread ;
345
+ } else {
346
+ // fiber.thread set by createThreadToReceiveFirstMessage()
347
+ }
319
348
320
349
final RubyThread rubyThread = fiber .rubyThread ;
321
350
@@ -354,26 +383,23 @@ public void killOtherFibers(RubyThread thread) {
354
383
boolean allowSideEffects = safepoint .setAllowSideEffects (false );
355
384
try {
356
385
final TruffleContext truffleContext = context .getEnv ().getContext ();
357
- context . getThreadManager (). leaveAndEnter (truffleContext , DummyNode .INSTANCE , ( ) -> {
386
+ truffleContext . leaveAndEnter (DummyNode .INSTANCE , Interrupter . THREAD_INTERRUPT , ( unused ) -> {
358
387
doKillOtherFibers (thread );
359
388
return BlockingAction .SUCCESS ;
360
- });
389
+ }, null );
361
390
} finally {
362
391
safepoint .setAllowSideEffects (allowSideEffects );
363
392
}
364
393
}
365
394
366
- private void doKillOtherFibers (RubyThread thread ) {
395
+ private void doKillOtherFibers (RubyThread thread ) throws InterruptedException {
367
396
for (RubyFiber fiber : thread .runningFibers ) {
368
397
if (!fiber .isRootFiber ()) {
369
398
addToMessageQueue (fiber , new FiberShutdownMessage ());
370
399
371
400
// Wait for the Fiber to finish so we only run one Fiber at a time
372
401
final CountDownLatch finishedLatch = fiber .finishedLatch ;
373
- context .getThreadManager ().retryWhileInterrupted (
374
- DummyNode .INSTANCE ,
375
- CountDownLatch ::await ,
376
- finishedLatch );
402
+ finishedLatch .await ();
377
403
378
404
final Throwable uncaughtException = fiber .uncaughtException ;
379
405
if (uncaughtException != null ) {
0 commit comments