Skip to content

Commit 5aee576

Browse files
committed
[GR-67821] Use system threads and polyglot threads for JDWP.
PullRequest: graal/21635
2 parents e7bc0ca + 596ff7f commit 5aee576

File tree

9 files changed

+153
-32
lines changed

9 files changed

+153
-32
lines changed

espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/api/JDWPContext.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.util.Set;
2828

2929
import com.oracle.truffle.api.TruffleLanguage;
30+
import com.oracle.truffle.api.TruffleLanguage.Env;
31+
import com.oracle.truffle.api.TruffleThreadBuilder;
3032
import com.oracle.truffle.api.frame.Frame;
3133
import com.oracle.truffle.api.nodes.Node;
3234
import com.oracle.truffle.api.nodes.RootNode;
@@ -221,6 +223,21 @@ public interface JDWPContext {
221223
*/
222224
Ids<Object> getIds();
223225

226+
/**
227+
* Creates a new system thread.
228+
*
229+
* @return {@link Env#createSystemThread(java.lang.Runnable)}.
230+
*/
231+
public Thread createSystemThread(Runnable runnable);
232+
233+
/**
234+
* Creates a new polyglot thread bound to the Espresso language context.
235+
*
236+
* @return {@link Env#newTruffleThreadBuilder(java.lang.Runnable)}.{@link TruffleThreadBuilder#build()
237+
* build()}.
238+
*/
239+
public Thread createPolyglotThread(Runnable runnable);
240+
224241
/**
225242
* @param string guest language string object
226243
* @return true if object is a guest language String, false otherwise

espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerConnection.java

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,13 @@
2424

2525
import java.io.IOException;
2626
import java.net.Socket;
27+
import java.util.concurrent.BlockingQueue;
2728
import java.util.concurrent.Callable;
2829
import java.util.concurrent.CountDownLatch;
30+
import java.util.concurrent.LinkedBlockingQueue;
31+
import java.util.concurrent.atomic.AtomicBoolean;
2932

33+
import com.oracle.truffle.api.TruffleSafepoint;
3034
import com.oracle.truffle.espresso.jdwp.api.ErrorCodes;
3135
import com.oracle.truffle.espresso.jdwp.api.JDWPContext;
3236

@@ -43,7 +47,8 @@ private DebuggerConnection(SocketConnection connection, DebuggerController contr
4347
}
4448

4549
static void establishDebuggerConnection(DebuggerController controller, DebuggerController.SetupState setupState, boolean isReconnect, CountDownLatch startupLatch) {
46-
Thread jdwpReceiver = new Thread(new JDWPReceiver(controller, setupState, isReconnect, startupLatch), "jdwp-receiver");
50+
Thread jdwpReceiver = controller.getContext().createSystemThread(new JDWPReceiver(controller, setupState, isReconnect, startupLatch));
51+
jdwpReceiver.setName("jdwp-receiver");
4752
controller.addDebuggerReceiverThread(jdwpReceiver);
4853
jdwpReceiver.setDaemon(true);
4954
jdwpReceiver.start();
@@ -104,25 +109,22 @@ public void run() {
104109

105110
private static class JDWPReceiver implements Runnable {
106111

107-
private static final Object NOT_ENTERED_MARKER = new Object();
108112
private DebuggerController.SetupState setupState;
109113
private final DebuggerController controller;
110-
private RequestedJDWPEvents requestedJDWPEvents;
111-
private DebuggerConnection debuggerConnection;
112114
private final boolean isReconnect;
113115
private final CountDownLatch latch;
114116

115117
JDWPReceiver(DebuggerController controller, DebuggerController.SetupState setupState, boolean isReconnect, CountDownLatch latch) {
116118
this.setupState = setupState;
117119
this.controller = controller;
118-
this.requestedJDWPEvents = new RequestedJDWPEvents(controller);
119120
this.isReconnect = isReconnect;
120121
this.latch = latch;
121122
}
122123

123124
@Override
124125
public void run() {
125126
// first, complete the connection setup which is potentially blocking
127+
DebuggerConnection debuggerConnection;
126128
try {
127129
Socket connectionSocket;
128130
if (setupState.socket != null) {
@@ -161,7 +163,8 @@ public void run() {
161163
}
162164

163165
// OK, we're ready to fire up the JDWP transmitter thread too
164-
Thread jdwpSender = new Thread(new JDWPSender(socketConnection), "jdwp-transmitter");
166+
Thread jdwpSender = controller.getContext().createSystemThread(new JDWPSender(socketConnection));
167+
jdwpSender.setName("jdwp-transmitter");
165168
controller.addDebuggerSenderThread(jdwpSender);
166169
jdwpSender.setDaemon(true);
167170
jdwpSender.start();
@@ -190,36 +193,75 @@ public void run() {
190193
latch.countDown();
191194
}
192195
// Now, begin processing packets when they start to flow from the debugger.
193-
// Make sure this thread is entered in the context
196+
final BlockingQueue<Packet> packetQueue = new LinkedBlockingQueue<>();
197+
final AtomicBoolean processorClose = new AtomicBoolean(false);
198+
Thread jdwpProcessor = controller.getContext().createPolyglotThread(new JDWPProcessor(controller, debuggerConnection, packetQueue, processorClose));
199+
jdwpProcessor.setName("jdwp-processor");
200+
controller.addDebuggerProcessorThread(jdwpProcessor);
201+
jdwpProcessor.setDaemon(true);
202+
jdwpProcessor.start();
194203
try {
195204
while (!Thread.currentThread().isInterrupted() && !controller.isClosing()) {
196-
Object previous = NOT_ENTERED_MARKER;
197205
try {
198-
// get the packet outside the Truffle context, because it's a blocking IO
199-
// operation
200206
Packet packet = Packet.fromByteArray(debuggerConnection.connection.readPacket());
201-
previous = controller.enterTruffleContext();
202-
processPacket(packet);
207+
packetQueue.add(packet);
203208
} catch (IOException e) {
204209
if (!debuggerConnection.isOpen()) {
205210
// when the socket is closed, we're done
206211
break;
207212
}
208213
if (!Thread.currentThread().isInterrupted()) {
209214
controller.warning(() -> "Failed to process jdwp packet with message: " + e.getMessage());
215+
Thread.currentThread().interrupt(); // And set the interrupt flag again
210216
}
211217
} catch (ConnectionClosedException e) {
212218
break;
213-
} finally {
214-
if (previous != NOT_ENTERED_MARKER) {
215-
controller.leaveTruffleContext(previous);
216-
}
217219
}
218220
}
219221
} finally {
222+
processorClose.set(true);
223+
jdwpProcessor.interrupt();
220224
controller.getEventListener().onDetach();
221225
}
222226
}
227+
}
228+
229+
private static class JDWPProcessor implements Runnable {
230+
231+
private final DebuggerController controller;
232+
private final DebuggerConnection debuggerConnection;
233+
private final RequestedJDWPEvents requestedJDWPEvents;
234+
private final BlockingQueue<Packet> packetQueue;
235+
private final AtomicBoolean close;
236+
237+
private JDWPProcessor(DebuggerController controller, DebuggerConnection debuggerConnection,
238+
BlockingQueue<Packet> packetQueue, AtomicBoolean close) {
239+
this.controller = controller;
240+
this.debuggerConnection = debuggerConnection;
241+
this.requestedJDWPEvents = new RequestedJDWPEvents(controller);
242+
this.packetQueue = packetQueue;
243+
this.close = close;
244+
}
245+
246+
@Override
247+
public void run() {
248+
while (!close.get()) {
249+
Packet packet;
250+
try {
251+
packet = TruffleSafepoint.getCurrent().setBlockedFunction(null, TruffleSafepoint.Interrupter.THREAD_INTERRUPT,
252+
BlockingQueue::take, packetQueue, () -> breakIfClosed(), null);
253+
} catch (ProcessorClosedException ex) {
254+
break;
255+
}
256+
processPacket(packet);
257+
}
258+
}
259+
260+
private void breakIfClosed() {
261+
if (close.get()) {
262+
throw new ProcessorClosedException();
263+
}
264+
}
223265

224266
private void processPacket(Packet packet) {
225267
JDWPContext context = controller.getContext();
@@ -665,6 +707,11 @@ private void processPacket(Packet packet) {
665707
debuggerConnection.handleReply(packet, new CommandResult(reply));
666708
}
667709
}
710+
711+
private static class ProcessorClosedException extends RuntimeException {
712+
713+
private static final long serialVersionUID = 8467327507834079474L;
714+
}
668715
}
669716

670717
private static CommandResult unknownCommandSet(Packet packet, DebuggerController controller) {

espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerController.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public final class DebuggerController implements ContextsListener {
8787
private JDWPContext context;
8888
private Thread senderThread;
8989
private Thread receiverThread;
90+
private Thread processorThread;
9091
private volatile HandshakeController hsController = null;
9192
private final Lock resetting = new ReentrantLock();
9293
private volatile boolean isClosing;
@@ -169,7 +170,7 @@ public void reInitialize() {
169170
DebuggerConnection.establishDebuggerConnection(newController, newController.setupState, true, new CountDownLatch(1));
170171
}
171172

172-
public void reset(boolean prepareForReconnect) {
173+
private void reset(boolean prepareForReconnect) {
173174
if (isClosing) {
174175
// already done closing, so don't attempt anything further
175176
return;
@@ -178,7 +179,6 @@ public void reset(boolean prepareForReconnect) {
178179
// mark that we're closing down the whole context
179180
isClosing = true;
180181
}
181-
Thread currentReceiverThread = null;
182182
try {
183183
// begin section that needs to be synchronized with establishing a new connection and
184184
// starting the threads. The logic within the locked part, must be written in a way that
@@ -190,8 +190,6 @@ public void reset(boolean prepareForReconnect) {
190190
// when resuming all threads
191191
endSession();
192192

193-
currentReceiverThread = receiverThread;
194-
195193
// Close the server socket used to listen for transport dt_socket.
196194
// This will unblock the accept call on a server socket.
197195
HandshakeController hsc = hsController;
@@ -225,9 +223,10 @@ public void reset(boolean prepareForReconnect) {
225223
resetting.unlock();
226224
}
227225

228-
// If we're not running in the receiver thread we should join
229-
if (Thread.currentThread() != currentReceiverThread) {
230-
joinThread(currentReceiverThread);
226+
joinThread(receiverThread);
227+
// If we're not running in the processor thread we should join
228+
if (Thread.currentThread() != processorThread) {
229+
joinThread(processorThread);
231230
}
232231

233232
if (prepareForReconnect && !isClosing && isServer()) {
@@ -274,16 +273,23 @@ public void closeSocket() {
274273
}
275274

276275
public void addDebuggerReceiverThread(Thread thread) {
276+
assert receiverThread == null;
277277
receiverThread = thread;
278278
}
279279

280+
public void addDebuggerProcessorThread(Thread thread) {
281+
assert processorThread == null;
282+
processorThread = thread;
283+
}
284+
280285
public void addDebuggerSenderThread(Thread thread) {
286+
assert senderThread == null;
281287
senderThread = thread;
282288
}
283289

284290
public boolean isDebuggerThread(Thread hostThread) {
285-
// only the receiver thread enters the context
286-
return hostThread == receiverThread;
291+
// only the procesor thread enters the context
292+
return hostThread == processorThread;
287293
}
288294

289295
public void markLateStartupError(Throwable t) {

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/JDWPContextImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ public Ids<Object> getIds() {
125125
return ids;
126126
}
127127

128+
@Override
129+
public Thread createSystemThread(Runnable runnable) {
130+
return context.getEnv().createSystemThread(runnable);
131+
}
132+
133+
@Override
134+
public Thread createPolyglotThread(Runnable runnable) {
135+
return context.getEnv().newTruffleThreadBuilder(runnable).build();
136+
}
137+
128138
@Override
129139
public boolean isString(Object string) {
130140
return Meta.isString(string);

truffle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan
77
* GR-66839: Deprecate `Location#isFinal()` as it always returns false.
88
* GR-67702: Specialization DSL: For nodes annotated with `@GenerateInline`, inlining warnings emitted for `@Cached SomeNode helper` expressions are now suppressed if the helper node class is explicitly annotated with `@GenerateInline(false)`, or is not a DSL node. This avoids unnecessary warnings if inlining for a node was explicitly disabled, and makes it possible to remove `@Cached(inline = false)` in most cases.
99
* GR-68165: `DynamicObjectLibrary#setDynamicType` transitions are now weak, like `DynamicObjectLibrary#putConstant` transitions.
10+
* GR-67821: `TruffleLanguage.Env#createSystemThread` is now allowed to be be called from a system thread now without an explicitly entered context.
1011

1112
## Version 25.0
1213
* GR-31495 Added ability to specify language and instrument specific options using `Source.Builder.option(String, String)`. Languages may describe available source options by implementing `TruffleLanguage.getSourceOptionDescriptors()` and `TruffleInstrument.getSourceOptionDescriptors()` respectively.

truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/LanguageSystemThreadTest.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -251,8 +251,16 @@ protected Object execute(RootNode node, Env env, Object[] contextArguments, Obje
251251
@Test
252252
public void testCreateSystemThreadNotEntered() {
253253
try (Context context1 = Context.newBuilder().build()) {
254-
String errorMessage = AbstractExecutableTestLanguage.evalTestLanguage(context1, CreateSystemThreadNotEnteredLanguage.class, "").asString();
255-
Assert.assertEquals("There is no current context available.", errorMessage);
254+
String successMessage = AbstractExecutableTestLanguage.evalTestLanguage(context1, CreateSystemThreadNotEnteredLanguage.class, "").asString();
255+
Assert.assertEquals("OK", successMessage);
256+
}
257+
}
258+
259+
@Test
260+
public void testCreateNewThreadNotEntered() {
261+
try (Context context1 = Context.newBuilder().build()) {
262+
String errorMessage = AbstractExecutableTestLanguage.evalTestLanguage(context1, CreateNewThreadNotEnteredLanguage.class, "").asString();
263+
Assert.assertEquals("Not entered in an Env's context.", errorMessage);
256264
}
257265
}
258266

@@ -264,6 +272,34 @@ public static final class CreateSystemThreadNotEnteredLanguage extends AbstractE
264272
protected Object execute(RootNode node, Env env, Object[] contextArguments, Object[] frameArguments) throws Exception {
265273
AtomicReference<Throwable> throwableRef = new AtomicReference<>();
266274
Thread t = env.createSystemThread(() -> {
275+
try {
276+
Thread systemT = env.createSystemThread(() -> {
277+
});
278+
// Can create system thread from a system thread.
279+
Assert.assertNotNull(systemT);
280+
} catch (Throwable exception) {
281+
throwableRef.set(exception);
282+
}
283+
});
284+
t.start();
285+
t.join();
286+
Throwable throwable = throwableRef.get();
287+
if (throwable != null) {
288+
return throwable.getMessage();
289+
} else {
290+
return "OK";
291+
}
292+
}
293+
}
294+
295+
@Registration
296+
public static final class CreateNewThreadNotEnteredLanguage extends AbstractExecutableTestLanguage {
297+
298+
@Override
299+
@TruffleBoundary
300+
protected Object execute(RootNode node, Env env, Object[] contextArguments, Object[] frameArguments) throws Exception {
301+
AtomicReference<Throwable> throwableRef = new AtomicReference<>();
302+
Thread t = new Thread(() -> {
267303
try {
268304
env.createSystemThread(() -> {
269305
});

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2016,8 +2016,8 @@ public Thread createSystemThread(Runnable runnable) {
20162016
* {@link TruffleLanguage#initializeThread(Object, Thread) languages} or instruments'
20172017
* thread-listeners. Creating a system thread does not cause a transition to multi-threaded
20182018
* access. The {@link Env#isCreateThreadAllowed() creation permit} is not required to create
2019-
* a system thread, but the caller must be entered in a context to create a system thread,
2020-
* if not an {@link IllegalStateException} is thrown.
2019+
* a system thread. The caller must be either entered in a context, or in another system
2020+
* thread to create a new system thread. If not an {@link IllegalStateException} is thrown.
20212021
* <p>
20222022
* It is recommended to set an
20232023
* {@link Thread#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)

truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2119,8 +2119,12 @@ public Thread createInstrumentSystemThread(Object polyglotInstrument, Runnable r
21192119
@Override
21202120
public Thread createLanguageSystemThread(Object polyglotLanguageContext, Runnable runnable, ThreadGroup threadGroup) {
21212121
PolyglotLanguageContext languageContext = (PolyglotLanguageContext) polyglotLanguageContext;
2122+
PolyglotContextImpl currentContext = PolyglotFastThreadLocals.getContext(null);
2123+
if (currentContext == null && Thread.currentThread() instanceof LanguageSystemThread systemThread) {
2124+
currentContext = systemThread.polyglotContext;
2125+
}
21222126
// Ensure that thread is entered in correct context
2123-
if (PolyglotContextImpl.requireContext() != languageContext.context) {
2127+
if (currentContext != languageContext.context) {
21242128
throw new IllegalStateException("Not entered in an Env's context.");
21252129
}
21262130
return new LanguageSystemThread(languageContext, runnable, threadGroup);

truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/SystemThread.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private void checkClosed() {
104104
static final class LanguageSystemThread extends SystemThread {
105105

106106
final String languageId;
107-
private final PolyglotContextImpl polyglotContext;
107+
final PolyglotContextImpl polyglotContext;
108108

109109
LanguageSystemThread(PolyglotLanguageContext polyglotLanguageContext, Runnable runnable, ThreadGroup threadGroup) {
110110
super(runnable, threadGroup, polyglotLanguageContext.context.engine.impl);

0 commit comments

Comments
 (0)