Skip to content

Commit 050e591

Browse files
Support for debugging virtual threads (#441)
* Support for debugging virtual threads
1 parent f9592bf commit 050e591

File tree

9 files changed

+154
-46
lines changed

9 files changed

+154
-46
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017 Microsoft Corporation and others.
2+
* Copyright (c) 2017-2022 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -11,6 +11,8 @@
1111

1212
package com.microsoft.java.debug.core;
1313

14+
import java.lang.reflect.InvocationTargetException;
15+
import java.lang.reflect.Method;
1416
import java.util.ArrayList;
1517
import java.util.List;
1618

@@ -31,18 +33,49 @@ public DebugSession(VirtualMachine virtualMachine) {
3133

3234
@Override
3335
public void start() {
36+
boolean supportsVirtualThreads = mayCreateVirtualThreads();
37+
3438
// request thread events by default
3539
EventRequest threadStartRequest = vm.eventRequestManager().createThreadStartRequest();
3640
threadStartRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
41+
if (supportsVirtualThreads) {
42+
addPlatformThreadsOnlyFilter(threadStartRequest);
43+
}
3744
threadStartRequest.enable();
3845

3946
EventRequest threadDeathRequest = vm.eventRequestManager().createThreadDeathRequest();
4047
threadDeathRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE);
48+
if (supportsVirtualThreads) {
49+
addPlatformThreadsOnlyFilter(threadDeathRequest);
50+
}
4151
threadDeathRequest.enable();
4252

4353
eventHub.start(vm);
4454
}
4555

56+
private boolean mayCreateVirtualThreads() {
57+
try {
58+
Method method = vm.getClass().getMethod("mayCreateVirtualThreads");
59+
return (boolean) method.invoke(vm);
60+
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
61+
// ignore
62+
}
63+
64+
return false;
65+
}
66+
67+
/**
68+
* For thread start and thread death events, restrict the events so they are only sent for platform threads.
69+
*/
70+
private void addPlatformThreadsOnlyFilter(EventRequest threadLifecycleRequest) {
71+
try {
72+
Method method = threadLifecycleRequest.getClass().getMethod("addPlatformThreadsOnlyFilter");
73+
method.invoke(threadLifecycleRequest);
74+
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
75+
// ignore
76+
}
77+
}
78+
4679
@Override
4780
public void suspend() {
4881
vm.suspend();

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313

1414
import java.util.ArrayList;
1515
import java.util.Collections;
16+
import java.util.HashSet;
1617
import java.util.LinkedHashMap;
1718
import java.util.List;
1819
import java.util.Map;
20+
import java.util.Set;
1921
import java.util.concurrent.ConcurrentHashMap;
2022

2123
import com.sun.jdi.ThreadReference;
@@ -29,6 +31,7 @@ protected boolean removeEldestEntry(java.util.Map.Entry<Long, Boolean> eldest) {
2931
return this.size() > 100;
3032
}
3133
});
34+
private Map<Long, ThreadReference> eventThreads = new ConcurrentHashMap<>();
3235

3336
public synchronized void resetThreads(List<ThreadReference> threads) {
3437
allThreads.clear();
@@ -46,6 +49,12 @@ public synchronized ThreadReference getThread(long threadId) {
4649
}
4750
}
4851

52+
for (ThreadReference thread : eventThreads.values()) {
53+
if (threadId == thread.uniqueID()) {
54+
return thread;
55+
}
56+
}
57+
4958
return null;
5059
}
5160

@@ -59,14 +68,49 @@ public String getThreadName(long threadId) {
5968

6069
public void addDeathThread(long threadId) {
6170
threadNameMap.remove(threadId);
71+
eventThreads.remove(threadId);
6272
deathThreads.put(threadId, true);
6373
}
6474

65-
public void removeDeathThread(long threadId) {
66-
deathThreads.remove(threadId);
67-
}
68-
6975
public boolean isDeathThread(long threadId) {
7076
return deathThreads.containsKey(threadId);
7177
}
78+
79+
public void addEventThread(ThreadReference thread) {
80+
eventThreads.put(thread.uniqueID(), thread);
81+
}
82+
83+
public void removeEventThread(long threadId) {
84+
eventThreads.remove(threadId);
85+
}
86+
87+
public void clearEventThread() {
88+
eventThreads.clear();
89+
}
90+
91+
/**
92+
* The visible threads includes:
93+
* 1. The currently running threads returned by the JDI API
94+
* VirtualMachine.allThreads().
95+
* 2. The threads suspended by events such as Breakpoint, Step, Exception etc.
96+
*
97+
* The part 2 is mainly for virtual threads, since VirtualMachine.allThreads()
98+
* does not include virtual threads by default. For those virtual threads
99+
* that are suspended, we need to show their call stacks in CALL STACK view.
100+
*/
101+
public List<ThreadReference> visibleThreads(IDebugAdapterContext context) {
102+
List<ThreadReference> visibleThreads = new ArrayList<>(context.getDebugSession().getAllThreads());
103+
Set<Long> idSet = new HashSet<>();
104+
visibleThreads.forEach(thread -> idSet.add(thread.uniqueID()));
105+
for (ThreadReference thread : eventThreads.values()) {
106+
if (idSet.contains(thread.uniqueID())) {
107+
continue;
108+
}
109+
110+
idSet.add(thread.uniqueID());
111+
visibleThreads.add(thread);
112+
}
113+
114+
return visibleThreads;
115+
}
72116
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017-2020 Microsoft Corporation and others.
2+
* Copyright (c) 2017-2022 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -109,15 +109,15 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
109109
// ignore since SetBreakpointsRequestHandler has already handled
110110
} else if (event instanceof ExceptionEvent) {
111111
ThreadReference thread = ((ExceptionEvent) event).thread();
112-
ThreadReference bpThread = ((ExceptionEvent) event).thread();
113112
IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class);
114-
if (engine.isInEvaluation(bpThread)) {
113+
if (engine.isInEvaluation(thread)) {
115114
return;
116115
}
117116

118117
JdiExceptionReference jdiException = new JdiExceptionReference(((ExceptionEvent) event).exception(),
119118
((ExceptionEvent) event).catchLocation() == null);
120119
context.getExceptionManager().setException(thread.uniqueID(), jdiException);
120+
context.getThreadCache().addEventThread(thread);
121121
context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID()));
122122
debugEvent.shouldResume = false;
123123
} else {

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ExceptionInfoRequestHandler.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2019 Microsoft Corporation and others.
2+
* Copyright (c) 2019-2022 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -53,7 +53,11 @@ public List<Command> getTargetCommands() {
5353
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
5454
IDebugAdapterContext context) {
5555
ExceptionInfoArguments exceptionInfoArgs = (ExceptionInfoArguments) arguments;
56-
ThreadReference thread = DebugUtility.getThread(context.getDebugSession(), exceptionInfoArgs.threadId);
56+
ThreadReference thread = context.getThreadCache().getThread(exceptionInfoArgs.threadId);
57+
if (thread == null) {
58+
thread = DebugUtility.getThread(context.getDebugSession(), exceptionInfoArgs.threadId);
59+
}
60+
5761
if (thread == null) {
5862
throw AdapterUtils.createCompletionException("Thread " + exceptionInfoArgs.threadId + " doesn't exist.", ErrorCode.EXCEPTION_INFO_FAILURE);
5963
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017 Microsoft Corporation and others.
2+
* Copyright (c) 2017-2022 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -207,12 +207,14 @@ private void registerBreakpointHandler(IDebugAdapterContext context) {
207207
if (resume) {
208208
debugEvent.eventSet.resume();
209209
} else {
210+
context.getThreadCache().addEventThread(bpThread);
210211
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
211212
breakpointName, bpThread.uniqueID()));
212213
}
213214
});
214215
});
215216
} else {
217+
context.getThreadCache().addEventThread(bpThread);
216218
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
217219
breakpointName, bpThread.uniqueID()));
218220
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2019 Microsoft Corporation and others.
2+
* Copyright (c) 2019-2022 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -151,11 +151,13 @@ private void registerWatchpointHandler(IDebugAdapterContext context) {
151151
if (resume) {
152152
debugEvent.eventSet.resume();
153153
} else {
154+
context.getThreadCache().addEventThread(bpThread);
154155
context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID()));
155156
}
156157
});
157158
});
158159
} else {
160+
context.getThreadCache().addEventThread(bpThread);
159161
context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID()));
160162
}
161163
debugEvent.shouldResume = false;

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,15 @@ private void registerMethodBreakpointHandler(IDebugAdapterContext context) {
165165
if (resume) {
166166
debugEvent.eventSet.resume();
167167
} else {
168+
context.getThreadCache().addEventThread(bpThread);
168169
context.getProtocolServer().sendEvent(new Events.StoppedEvent(
169170
"function breakpoint", bpThread.uniqueID()));
170171
}
171172
});
172173
});
173174

174175
} else {
176+
context.getThreadCache().addEventThread(bpThread);
175177
context.getProtocolServer()
176178
.sendEvent(new Events.StoppedEvent("function breakpoint", bpThread.uniqueID()));
177179
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
178178
threadState.pendingMethodExitRequest.enable();
179179
}
180180

181+
context.getThreadCache().removeEventThread(thread.uniqueID());
181182
DebugUtility.resumeThread(thread);
182183
ThreadsRequestHandler.checkThreadRunningAndRecycleIds(thread, context);
183184
} catch (IncompatibleThreadStateException ex) {
@@ -255,6 +256,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
255256
if (threadState.eventSubscription != null) {
256257
threadState.eventSubscription.dispose();
257258
}
259+
context.getThreadCache().addEventThread(thread);
258260
context.getProtocolServer().sendEvent(new Events.StoppedEvent("step", thread.uniqueID()));
259261
debugEvent.shouldResume = false;
260262
} else if (event instanceof MethodExitEvent) {

0 commit comments

Comments
 (0)