Skip to content

Commit dea29f5

Browse files
Enable async jdwp based on network latency for auto mode (#447)
1 parent 195266e commit dea29f5

File tree

6 files changed

+90
-13
lines changed

6 files changed

+90
-13
lines changed

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public class DebugAdapterContext implements IDebugAdapterContext {
6161
private long processId = -1;
6262

6363
private boolean localDebugging = true;
64+
private long jdwpLatency = 0;
6465

6566
private IdCollection<String> sourceReferences = new IdCollection<>();
6667
private RecyclableObjectPool<Long, Object> recyclableIdPool = new RecyclableObjectPool<>();
@@ -372,7 +373,21 @@ public ThreadCache getThreadCache() {
372373

373374
@Override
374375
public boolean asyncJDWP() {
375-
return DebugSettings.getCurrent().asyncJDWP == AsyncMode.ON;
376+
/**
377+
* If we take 1 second as the acceptable latency for DAP requests,
378+
* With a single-threaded strategy for handling JDWP requests,
379+
* a latency of about 15ms per JDWP request can ensure the responsiveness
380+
* for most DAPs. It allows sending 66 JDWP requests within 1 seconds,
381+
* which can cover most DAP operations such as breakpoint, threads,
382+
* call stack, step and continue.
383+
*/
384+
return asyncJDWP(15);
385+
}
386+
387+
@Override
388+
public boolean asyncJDWP(long usableLatency) {
389+
return DebugSettings.getCurrent().asyncJDWP == AsyncMode.ON
390+
|| (DebugSettings.getCurrent().asyncJDWP == AsyncMode.AUTO && this.jdwpLatency > usableLatency);
376391
}
377392

378393
public boolean isLocalDebugging() {
@@ -382,4 +397,14 @@ public boolean isLocalDebugging() {
382397
public void setLocalDebugging(boolean local) {
383398
this.localDebugging = local;
384399
}
400+
401+
@Override
402+
public long getJDWPLatency() {
403+
return this.jdwpLatency;
404+
}
405+
406+
@Override
407+
public void setJDWPLatency(long baseLatency) {
408+
this.jdwpLatency = baseLatency;
409+
}
385410
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,13 @@ public interface IDebugAdapterContext {
145145

146146
boolean asyncJDWP();
147147

148+
boolean asyncJDWP(long usableLatency/**ms*/);
149+
148150
boolean isLocalDebugging();
149151

150152
void setLocalDebugging(boolean local);
153+
154+
long getJDWPLatency();
155+
156+
void setJDWPLatency(long baseLatency);
151157
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,10 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
110110
long sent = System.currentTimeMillis();
111111
request.enable();
112112
long received = System.currentTimeMillis();
113-
logger.info("Network latency for JDWP command: " + (received - sent) + "ms");
114-
traceInfo.put("networkLatency", (received - sent));
113+
long latency = received - sent;
114+
context.setJDWPLatency(latency);
115+
logger.info("Network latency for JDWP command: " + latency + "ms");
116+
traceInfo.put("networkLatency", latency);
115117
}
116118

117119
IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class);

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017-2021 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
@@ -65,7 +65,9 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
6565
String expression = evalArguments.expression;
6666

6767
// Async mode is supposed to be performant, then disable the advanced features like hover evaluation.
68-
if (!context.isLocalDebugging() && context.asyncJDWP() && "hover".equals(evalArguments.context)) {
68+
if (context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY)
69+
&& context.getJDWPLatency() > VariablesRequestHandler.USABLE_JDWP_LATENCY
70+
&& "hover".equals(evalArguments.context)) {
6971
return CompletableFuture.completedFuture(response);
7072
}
7173

@@ -98,7 +100,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
98100
Value sizeValue = null;
99101
if (value instanceof ArrayReference) {
100102
indexedVariables = ((ArrayReference) value).length();
101-
} else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure && engine != null) {
103+
} else if (value instanceof ObjectReference && supportsLogicStructureView(context, evalArguments.context) && engine != null) {
102104
try {
103105
JavaLogicalStructure structure = JavaLogicalStructureManager.getLogicalStructure((ObjectReference) value);
104106
if (structure != null && structure.getSizeExpression() != null) {
@@ -135,7 +137,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
135137
// If failed to resolve the variable value, skip the details info as well.
136138
} else if (sizeValue != null) {
137139
detailsString = "size=" + variableFormatter.valueToString(sizeValue, options);
138-
} else if (DebugSettings.getCurrent().showToString) {
140+
} else if (supportsToStringView(context, evalArguments.context)) {
139141
try {
140142
detailsString = VariableDetailUtils.formatDetailsValue(value, stackFrameReference.getThread(), variableFormatter, options, engine);
141143
} catch (OutOfMemoryError e) {
@@ -182,4 +184,24 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
182184
}
183185
});
184186
}
187+
188+
private boolean supportsLogicStructureView(IDebugAdapterContext context, String evalContext) {
189+
if (!"watch".equals(evalContext)) {
190+
return true;
191+
}
192+
193+
return (!context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY)
194+
|| context.getJDWPLatency() <= VariablesRequestHandler.USABLE_JDWP_LATENCY)
195+
&& DebugSettings.getCurrent().showLogicalStructure;
196+
}
197+
198+
private boolean supportsToStringView(IDebugAdapterContext context, String evalContext) {
199+
if (!"watch".equals(evalContext)) {
200+
return true;
201+
}
202+
203+
return (!context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY)
204+
|| context.getJDWPLatency() <= VariablesRequestHandler.USABLE_JDWP_LATENCY)
205+
&& DebugSettings.getCurrent().showToString;
206+
}
185207
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
8282
}
8383

8484
// Async mode is supposed to be performant, then disable the advanced features like inline values.
85-
if (!context.isLocalDebugging() && context.asyncJDWP()) {
85+
if (context.getJDWPLatency() > VariablesRequestHandler.USABLE_JDWP_LATENCY
86+
&& context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY)) {
8687
response.body = new Responses.InlineValuesResponse(null);
8788
return CompletableFuture.completedFuture(response);
8889
}

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,21 @@
6868

6969
public class VariablesRequestHandler implements IDebugRequestHandler {
7070
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
71+
/**
72+
* When the debugger enables logical structures and
73+
* toString settings, for each Object variable in the
74+
* variable list, the debugger needs to check its
75+
* superclass and interface to find out if it inherits
76+
* from Collection or overrides the toString method.
77+
* This will cause the debugger to send a lot of JDWP
78+
* requests for them. For a test case with 4 object
79+
* variables, the debug adapter may need to send more
80+
* than 100 JDWP requests to handle these variable
81+
* requests. To achieve a DAP latency of 1s with a
82+
* single-threaded JDWP request processing strategy,
83+
* a single JDWP latency is about 10ms.
84+
*/
85+
static final long USABLE_JDWP_LATENCY = 10/**ms*/;
7186

7287
@Override
7388
public List<Command> getTargetCommands() {
@@ -130,7 +145,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
130145
childrenList.add(new Variable(returnIcon + result.method.name() + "()", result.value, null));
131146
}
132147

133-
if (context.asyncJDWP()) {
148+
if (useAsyncJDWP(context)) {
134149
childrenList.addAll(getVariablesOfFrameAsync(frame, showStaticVariables));
135150
} else {
136151
childrenList.addAll(VariableUtils.listLocalVariables(frame));
@@ -198,7 +213,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
198213
if (varArgs.count > 0) {
199214
childrenList = VariableUtils.listFieldVariables(containerObj, varArgs.start, varArgs.count);
200215
} else {
201-
childrenList = VariableUtils.listFieldVariables(containerObj, showStaticVariables, context.asyncJDWP());
216+
childrenList = VariableUtils.listFieldVariables(containerObj, showStaticVariables, useAsyncJDWP(context));
202217
}
203218
}
204219
} catch (AbsentInformationException e) {
@@ -215,7 +230,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
215230
.filter(var -> duplicateNames.contains(var.name))
216231
.collect(Collectors.toList());
217232
// Since JDI caches the fetched properties locally, in async mode we can warm up the JDI cache in advance.
218-
if (context.asyncJDWP()) {
233+
if (useAsyncJDWP(context)) {
219234
try {
220235
AsyncJdwpUtils.await(warmUpJDICache(childrenList, duplicateVars));
221236
} catch (CompletionException | CancellationException e) {
@@ -377,11 +392,17 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
377392
}
378393

379394
private boolean supportsLogicStructureView(IDebugAdapterContext context) {
380-
return (!context.asyncJDWP() || context.isLocalDebugging()) && DebugSettings.getCurrent().showLogicalStructure;
395+
return (!useAsyncJDWP(context) || context.getJDWPLatency() <= USABLE_JDWP_LATENCY)
396+
&& DebugSettings.getCurrent().showLogicalStructure;
381397
}
382398

383399
private boolean supportsToStringView(IDebugAdapterContext context) {
384-
return (!context.asyncJDWP() || context.isLocalDebugging()) && DebugSettings.getCurrent().showToString;
400+
return (!useAsyncJDWP(context) || context.getJDWPLatency() <= USABLE_JDWP_LATENCY)
401+
&& DebugSettings.getCurrent().showToString;
402+
}
403+
404+
private boolean useAsyncJDWP(IDebugAdapterContext context) {
405+
return context.asyncJDWP(USABLE_JDWP_LATENCY);
385406
}
386407

387408
private Types.Variable resolveLazyVariable(IDebugAdapterContext context, VariableProxy containerNode, IVariableFormatter variableFormatter,

0 commit comments

Comments
 (0)