Skip to content

Commit 2b2274e

Browse files
committed
refactor and simplify disposing debugger connection in a thread safe manner
1 parent 956ca1a commit 2b2274e

File tree

8 files changed

+248
-250
lines changed

8 files changed

+248
-250
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -904,8 +904,7 @@ public boolean vmDied() {
904904
}
905905
stream.writeByte(RequestedJDWPEvents.VM_DEATH);
906906
stream.writeInt(0);
907-
// don't queue this packet, send immediately
908-
connection.sendVMDied(stream, debuggerController);
907+
connection.queuePacket(stream);
909908
return vmDeathSuspendPolicy != SuspendStrategy.NONE;
910909
}
911910

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

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,19 @@ private DebuggerConnection(SocketConnection connection, DebuggerController contr
4242
this.context = controller.getContext();
4343
}
4444

45-
static void establishDebuggerConnection(DebuggerController controller, DebuggerController.SetupState setupState, boolean isReconnect, CountDownLatch latch) {
46-
Thread jdwpReceiver = new Thread(new JDWPReceiver(controller, setupState, isReconnect, latch), "jdwp-receiver");
47-
controller.addDebuggerThread(jdwpReceiver);
45+
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");
47+
controller.addDebuggerReceiverThread(jdwpReceiver);
4848
jdwpReceiver.setDaemon(true);
4949
jdwpReceiver.start();
5050
}
5151

52-
static void reconnectDebuggerConnection(DebuggerController controller, DebuggerController.SetupState setupState) {
53-
// On reconnect, we just pass a placeholder CountDownLatch object which we don't ever wait
54-
// for. This avoids tedious null checks in the connection method.
55-
establishDebuggerConnection(controller, setupState, true, new CountDownLatch(1));
52+
public void dispose() {
53+
connection.dispose();
5654
}
5755

58-
public void close() {
59-
try {
60-
connection.close(controller);
61-
} catch (IOException e) {
62-
throw new RuntimeException("Closing socket connection failed", e);
63-
}
56+
public void closeSocket() {
57+
connection.closeSocket();
6458
}
6559

6660
@Override
@@ -115,9 +109,7 @@ private static class JDWPSender implements Runnable {
115109

116110
@Override
117111
public void run() {
118-
while (!Thread.currentThread().isInterrupted()) {
119-
socketConnection.awaitSendPacket();
120-
}
112+
socketConnection.sendPackets();
121113
}
122114
}
123115

@@ -154,27 +146,50 @@ public void run() {
154146
if (!HandshakeController.handshake(connectionSocket)) {
155147
throw new IOException("Unable to handshake with debugger");
156148
}
157-
SocketConnection socketConnection = new SocketConnection(connectionSocket);
158-
debuggerConnection = new DebuggerConnection(socketConnection, controller);
159-
controller.setDebuggerConnection(debuggerConnection);
160-
controller.getEventListener().setConnection(socketConnection);
161-
if (!controller.isSuspend()) {
162-
// Fire the vm started event for the suspend=n case.
163-
// For suspend=y we have to synchronize the sending of VM started event with
164-
// the thread suspension count. Therefore, in that case we postpone the sending
165-
// until we can also suspend the main thread which is done in
166-
// DebuggerController#onLanguageContextInitialized.
167-
controller.getEventListener().vmStarted(false);
168-
}
149+
try {
150+
if (controller.isClosing()) {
151+
return;
152+
}
153+
// The following block has to be synchronized with resetting, so that
154+
// we can abandon further work in case we're told to tear down
155+
controller.getResettingLock().lockInterruptibly();
156+
// re-check to return immediately if closing
157+
if (controller.isClosing()) {
158+
return;
159+
}
160+
SocketConnection socketConnection = new SocketConnection(connectionSocket);
161+
debuggerConnection = new DebuggerConnection(socketConnection, controller);
162+
controller.setDebuggerConnection(debuggerConnection);
163+
controller.getEventListener().setConnection(socketConnection);
164+
if (!controller.isSuspend()) {
165+
// Fire the vm started event for the suspend=n case.
166+
// For suspend=y we have to synchronize the sending of VM started event with
167+
// the thread suspension count. Therefore, in that case we postpone the
168+
// sending until we can also suspend the main thread which is done in
169+
// DebuggerController#onLanguageContextInitialized.
170+
controller.getEventListener().vmStarted(false);
171+
}
169172

170-
// OK, we're ready to fire up the JDWP transmitter thread too
171-
Thread jdwpSender = new Thread(new JDWPSender(socketConnection), "jdwp-transmitter");
172-
controller.addDebuggerThread(jdwpSender);
173-
jdwpSender.setDaemon(true);
174-
jdwpSender.start();
173+
// OK, we're ready to fire up the JDWP transmitter thread too
174+
Thread jdwpSender = new Thread(new JDWPSender(socketConnection), "jdwp-transmitter");
175+
controller.addDebuggerSenderThread(jdwpSender);
176+
jdwpSender.setDaemon(true);
177+
jdwpSender.start();
178+
} catch (InterruptedException e) {
179+
Thread.currentThread().interrupt();
180+
return;
181+
} finally {
182+
controller.getResettingLock().unlock();
183+
}
175184
} catch (IOException ex) {
176185
if (isReconnect) {
177-
System.err.println("ERROR: Debuggers will not be able to connect to this context again!");
186+
// could be because we're closing down the context, so we should check that and
187+
// silently abort the re-connecting attempt
188+
if (controller.isClosing()) {
189+
return;
190+
} else {
191+
System.err.println("ERROR: Debuggers will not be able to connect to this context again!");
192+
}
178193
} else {
179194
// on startup any connection error is treated as fatal
180195
controller.markLateStartupError(ex);
@@ -190,11 +205,15 @@ public void run() {
190205
try {
191206
processPacket(Packet.fromByteArray(debuggerConnection.connection.readPacket()));
192207
} catch (IOException e) {
208+
if (!debuggerConnection.isOpen()) {
209+
// when the socket is closed, we're done
210+
break;
211+
}
193212
if (!Thread.currentThread().isInterrupted()) {
194213
controller.warning(() -> "Failed to process jdwp packet with message: " + e.getMessage());
195214
}
196215
} catch (ConnectionClosedException e) {
197-
// we closed the session, so let the thread run dry
216+
break;
198217
}
199218
}
200219
} finally {
@@ -244,7 +263,7 @@ private void processPacket(Packet packet) {
244263
result = JDWP.VirtualMachine.RESUME.createReply(packet, controller);
245264
break;
246265
case JDWP.VirtualMachine.EXIT.ID:
247-
result = JDWP.VirtualMachine.EXIT.createReply(packet, context);
266+
result = JDWP.VirtualMachine.EXIT.createReply(packet, context, controller);
248267
break;
249268
case JDWP.VirtualMachine.CREATE_STRING.ID:
250269
result = JDWP.VirtualMachine.CREATE_STRING.createReply(packet, context);

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

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.Map;
3434
import java.util.concurrent.Callable;
3535
import java.util.concurrent.CountDownLatch;
36+
import java.util.concurrent.locks.Lock;
3637
import java.util.function.Supplier;
3738
import java.util.logging.Level;
3839
import java.util.regex.Pattern;
@@ -131,11 +132,13 @@ public void reInitialize() {
131132
assert setupState != null;
132133

133134
if (setupState.fatalConnectionError) {
134-
System.err.println("ERROR: Debuggers will not be able to connect to this context again!");
135+
fine(() -> "Failed debugger setup due to initial connection issue.");
135136
// OK, give up on trying to reconnect
136137
return;
137138
}
138-
DebuggerConnection.reconnectDebuggerConnection(this, setupState);
139+
// On reconnect, we just pass a placeholder CountDownLatch object which we don't ever wait
140+
// for. This avoids tedious null checks in the connection method.
141+
DebuggerConnection.establishDebuggerConnection(this, setupState, true, new CountDownLatch(1));
139142
}
140143

141144
void setDebuggerConnection(DebuggerConnection connection) {
@@ -146,20 +149,38 @@ void setSetupState(SetupState state) {
146149
this.setupState = state;
147150
}
148151

149-
public void closeConnection() {
152+
public void dispose() {
150153
if (connection != null) {
151-
connection.close();
154+
connection.dispose();
152155
}
153156
}
154157

155-
public void addDebuggerThread(Thread thread) {
156-
instrument.addDebuggerThread(thread);
158+
public void closeSocket() {
159+
if (connection != null) {
160+
connection.closeSocket();
161+
}
162+
}
163+
164+
public void addDebuggerReceiverThread(Thread thread) {
165+
instrument.addDebuggerReceiverThread(thread);
166+
}
167+
168+
public void addDebuggerSenderThread(Thread thread) {
169+
instrument.addDebuggerSenderThread(thread);
157170
}
158171

159172
public void markLateStartupError(Throwable t) {
160173
lateStartupError = t;
161174
}
162175

176+
public boolean isClosing() {
177+
return instrument.isClosing();
178+
}
179+
180+
public Lock getResettingLock() {
181+
return instrument.getResettingLock();
182+
}
183+
163184
static final class SetupState {
164185
final Socket socket;
165186
final ServerSocket serverSocket;
@@ -505,15 +526,7 @@ public void disposeDebugger(boolean prepareReconnect) {
505526
suspend(context.asGuestThread(Thread.currentThread()), SuspendStrategy.EVENT_THREAD, Collections.emptyList(), true);
506527
}
507528
}
508-
// Creating a new thread, because the reset method
509-
// will interrupt all active jdwp threads, which might
510-
// include the current one if we received a DISPOSE command.
511-
new Thread(new Runnable() {
512-
@Override
513-
public void run() {
514-
instrument.reset(prepareReconnect);
515-
}
516-
}).start();
529+
instrument.reset(prepareReconnect);
517530
}
518531

519532
public void endSession() {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ void setupInitialConnection(DebuggerController controller) throws IOException {
5151
if (controller.isServer()) {
5252
ServerSocket serverSocket = new ServerSocket();
5353
serverSocket.setSoTimeout(0); // no timeout
54-
serverSocket.setReuseAddress(true);
54+
if (port != 0) {
55+
serverSocket.setReuseAddress(true);
56+
}
5557
if ("*".equals(controller.getHost())) {
5658
// allow any host to bind
5759
serverSocket.bind(new InetSocketAddress((InetAddress) null, port));

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

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,17 @@
2727
import java.util.ArrayList;
2828
import java.util.Collections;
2929
import java.util.List;
30-
import java.util.concurrent.Callable;
3130

3231
import com.oracle.truffle.api.interop.InteropException;
32+
import com.oracle.truffle.espresso.classfile.attributes.LineNumberTableRef;
33+
import com.oracle.truffle.espresso.classfile.attributes.LocalRef;
3334
import com.oracle.truffle.espresso.jdwp.api.CallFrame;
3435
import com.oracle.truffle.espresso.jdwp.api.ClassStatusConstants;
3536
import com.oracle.truffle.espresso.jdwp.api.ErrorCodes;
3637
import com.oracle.truffle.espresso.jdwp.api.FieldRef;
3738
import com.oracle.truffle.espresso.jdwp.api.JDWPConstantPool;
3839
import com.oracle.truffle.espresso.jdwp.api.JDWPContext;
3940
import com.oracle.truffle.espresso.jdwp.api.KlassRef;
40-
import com.oracle.truffle.espresso.classfile.attributes.LineNumberTableRef;
41-
import com.oracle.truffle.espresso.classfile.attributes.LocalRef;
4241
import com.oracle.truffle.espresso.jdwp.api.MethodRef;
4342
import com.oracle.truffle.espresso.jdwp.api.ModuleRef;
4443
import com.oracle.truffle.espresso.jdwp.api.MonitorStackInfo;
@@ -163,12 +162,9 @@ static class DISPOSE {
163162
static CommandResult createReply(Packet packet, DebuggerController controller) {
164163
PacketStream reply = new PacketStream().replyPacket().id(packet.id);
165164

166-
return new CommandResult(reply, null, Collections.singletonList(new Callable<Void>() {
167-
@Override
168-
public Void call() throws Exception {
169-
controller.disposeDebugger(true);
170-
return null;
171-
}
165+
return new CommandResult(reply, null, Collections.singletonList(() -> {
166+
controller.disposeDebugger(true);
167+
return null;
172168
}));
173169
}
174170
}
@@ -214,19 +210,17 @@ static CommandResult createReply(Packet packet, DebuggerController controller) {
214210
static class EXIT {
215211
public static final int ID = 10;
216212

217-
static CommandResult createReply(Packet packet, JDWPContext context) {
213+
static CommandResult createReply(Packet packet, JDWPContext context, DebuggerController controller) {
218214
PacketStream input = new PacketStream(packet);
219215
PacketStream reply = new PacketStream().replyPacket().id(packet.id);
220216

221217
if (context.systemExitImplemented()) {
222218
return new CommandResult(reply,
223219
null,
224-
Collections.singletonList(new Callable<Void>() {
225-
@Override
226-
public Void call() {
227-
context.exit(input.readInt());
228-
return null;
229-
}
220+
Collections.singletonList(() -> {
221+
controller.disposeDebugger(false);
222+
context.exit(input.readInt());
223+
return null;
230224
}));
231225
} else {
232226
reply.errorCode(ErrorCodes.NOT_IMPLEMENTED);

0 commit comments

Comments
 (0)