Skip to content

Commit 388b5fc

Browse files
[Refactor] use CompletableFuture to support async response in debug adapter (microsoft#112)
* [Refactor] use CompletableFuture to support async response in debug adapter Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Return an sync response for unrecognized request Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Use CompletableFuture.completedFuture to return async response Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * fix review comments Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * rename ProtocolServer.start method name to run Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Chain the CompletableFuture to support multiple DebugRequestHandlers handling a request Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
1 parent 1eb8bc0 commit 388b5fc

23 files changed

+238
-263
lines changed

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

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.nio.file.Paths;
2424
import java.security.MessageDigest;
2525
import java.security.NoSuchAlgorithmException;
26+
import java.util.concurrent.CompletableFuture;
2627
import java.util.regex.Matcher;
2728
import java.util.regex.Pattern;
2829

@@ -170,36 +171,54 @@ public static boolean isUri(String uriString) {
170171
}
171172

172173
/**
173-
* Generate an error response with the given error message.
174+
* Populate the response body with the given error message, and mark the success flag to false. At last return the response object back.
174175
*
175176
* @param response
176177
* the response object
177178
* @param errorCode
178179
* the error code
179180
* @param errorMessage
180181
* the error message
182+
* @return the modified response object.
181183
*/
182-
public static void setErrorResponse(Response response, ErrorCode errorCode, String errorMessage) {
184+
public static Response setErrorResponse(Response response, ErrorCode errorCode, String errorMessage) {
183185
response.body = new Responses.ErrorResponseBody(new Types.Message(errorCode.getId(), errorMessage));
184186
response.message = errorMessage;
185187
response.success = false;
188+
return response;
186189
}
187190

188191
/**
189-
* Generate an error response with the given exception.
192+
* Populate the response body with the given exception, and mark the success flag to false. At last return the response object back.
190193
*
191194
* @param response
192195
* the response object
193196
* @param errorCode
194197
* the error code
195198
* @param e
196199
* the exception
200+
* @return the modified response object.
197201
*/
198-
public static void setErrorResponse(Response response, ErrorCode errorCode, Exception e) {
202+
public static Response setErrorResponse(Response response, ErrorCode errorCode, Exception e) {
199203
String errorMessage = e.toString();
200204
response.body = new Responses.ErrorResponseBody(new Types.Message(errorCode.getId(), errorMessage));
201205
response.message = errorMessage;
202206
response.success = false;
207+
return response;
208+
}
209+
210+
/**
211+
* Generate a CompletableFuture response with the given error message.
212+
*/
213+
public static CompletableFuture<Response> createAsyncErrorResponse(Response response, ErrorCode errorCode, String errorMessage) {
214+
return CompletableFuture.completedFuture(setErrorResponse(response, errorCode, errorMessage));
215+
}
216+
217+
/**
218+
* Generate a CompletableFuture response with the given exception.
219+
*/
220+
public static CompletableFuture<Response> createAsyncErrorResponse(Response response, ErrorCode errorCode, Exception e) {
221+
return CompletableFuture.completedFuture(setErrorResponse(response, errorCode, e));
203222
}
204223

205224
/**

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

Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
import java.util.HashMap;
1616
import java.util.List;
1717
import java.util.Map;
18-
import java.util.function.BiConsumer;
18+
import java.util.concurrent.CompletableFuture;
19+
import java.util.function.Consumer;
1920
import java.util.logging.Level;
2021
import java.util.logging.Logger;
2122

@@ -34,7 +35,6 @@
3435
import com.microsoft.java.debug.core.adapter.handler.StackTraceRequestHandler;
3536
import com.microsoft.java.debug.core.adapter.handler.ThreadsRequestHandler;
3637
import com.microsoft.java.debug.core.adapter.handler.VariablesRequestHandler;
37-
import com.microsoft.java.debug.core.protocol.Events;
3838
import com.microsoft.java.debug.core.protocol.JsonUtils;
3939
import com.microsoft.java.debug.core.protocol.Messages;
4040
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
@@ -43,24 +43,20 @@
4343
public class DebugAdapter implements IDebugAdapter {
4444
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
4545

46-
private BiConsumer<Events.DebugEvent, Boolean> eventConsumer;
47-
private IProviderContext providerContext;
4846
private IDebugAdapterContext debugContext = null;
4947
private Map<Command, List<IDebugRequestHandler>> requestHandlers = null;
5048

5149
/**
5250
* Constructor.
5351
*/
54-
public DebugAdapter(BiConsumer<Events.DebugEvent, Boolean> consumer, IProviderContext providerContext) {
55-
eventConsumer = consumer;
56-
this.providerContext = providerContext;
57-
debugContext = new DebugAdapterContext(this);
52+
public DebugAdapter(Consumer<Messages.ProtocolMessage> messageConsumer, IProviderContext providerContext) {
53+
debugContext = new DebugAdapterContext(messageConsumer, providerContext);
5854
requestHandlers = new HashMap<>();
5955
initialize();
6056
}
6157

6258
@Override
63-
public Messages.Response dispatchRequest(Messages.Request request) {
59+
public CompletableFuture<Messages.Response> dispatchRequest(Messages.Request request) {
6460
Messages.Response response = new Messages.Response();
6561
response.request_seq = request.seq;
6662
response.command = request.command;
@@ -69,49 +65,24 @@ public Messages.Response dispatchRequest(Messages.Request request) {
6965
Command command = Command.parse(request.command);
7066
Arguments cmdArgs = JsonUtils.fromJson(request.arguments, command.getArgumentType());
7167

72-
try {
73-
if (debugContext.isVmTerminated()) {
74-
// the operation is meaningless
75-
return response;
76-
}
77-
List<IDebugRequestHandler> handlers = requestHandlers.get(command);
78-
if (handlers != null && !handlers.isEmpty()) {
79-
for (IDebugRequestHandler handler : handlers) {
80-
handler.handle(command, cmdArgs, response, debugContext);
81-
}
82-
} else {
83-
AdapterUtils.setErrorResponse(response, ErrorCode.UNRECOGNIZED_REQUEST_FAILURE,
84-
String.format("Unrecognized request: { _request: %s }", request.command));
68+
if (debugContext.isVmTerminated()) {
69+
// the operation is meaningless
70+
return CompletableFuture.completedFuture(response);
71+
}
72+
List<IDebugRequestHandler> handlers = requestHandlers.get(command);
73+
if (handlers != null && !handlers.isEmpty()) {
74+
CompletableFuture<Messages.Response> future = CompletableFuture.completedFuture(response);
75+
for (IDebugRequestHandler handler : handlers) {
76+
future = future.thenCompose((res) -> {
77+
return handler.handle(command, cmdArgs, res, debugContext);
78+
});
8579
}
86-
} catch (Exception e) {
87-
logger.log(Level.SEVERE, String.format("DebugSession dispatch exception: %s", e.toString()), e);
88-
AdapterUtils.setErrorResponse(response, ErrorCode.UNKNOWN_FAILURE,
89-
e.getMessage() != null ? e.getMessage() : e.toString());
80+
return future;
81+
} else {
82+
final String errorMessage = String.format("Unrecognized request: { _request: %s }", request.command);
83+
logger.log(Level.SEVERE, errorMessage);
84+
return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.UNRECOGNIZED_REQUEST_FAILURE, errorMessage);
9085
}
91-
92-
return response;
93-
}
94-
95-
/**
96-
* Send event to DA immediately.
97-
*
98-
* @see ProtocolServer#sendEvent(String, Object)
99-
*/
100-
public void sendEvent(Events.DebugEvent event) {
101-
eventConsumer.accept(event, false);
102-
}
103-
104-
/**
105-
* Send event to DA after the current dispatching request is resolved.
106-
*
107-
* @see ProtocolServer#sendEventLater(String, Object)
108-
*/
109-
public void sendEventLater(Events.DebugEvent event) {
110-
eventConsumer.accept(event, true);
111-
}
112-
113-
public <T extends IProvider> T getProvider(Class<T> clazz) {
114-
return providerContext.getProvider(clazz);
11586
}
11687

11788
private void initialize() {

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@
1414
import java.nio.charset.Charset;
1515
import java.util.Collections;
1616
import java.util.Map;
17+
import java.util.function.Consumer;
1718

1819
import com.microsoft.java.debug.core.IDebugSession;
1920
import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter;
2021
import com.microsoft.java.debug.core.adapter.variables.VariableFormatterFactory;
2122
import com.microsoft.java.debug.core.protocol.Events.DebugEvent;
23+
import com.microsoft.java.debug.core.protocol.Messages;
2224

2325
public class DebugAdapterContext implements IDebugAdapterContext {
2426
private static final int MAX_CACHE_ITEMS = 10000;
2527
private Map<String, String> sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS));
26-
private DebugAdapter debugAdapter;
28+
private IProviderContext providerContext;
29+
private Consumer<Messages.ProtocolMessage> messageConsumer;
2730

2831
private IDebugSession debugSession;
2932
private boolean debuggerLinesStartAt1 = true;
@@ -41,23 +44,19 @@ public class DebugAdapterContext implements IDebugAdapterContext {
4144
private RecyclableObjectPool<Long, Object> recyclableIdPool = new RecyclableObjectPool<>();
4245
private IVariableFormatter variableFormatter = VariableFormatterFactory.createVariableFormatter();
4346

44-
public DebugAdapterContext(DebugAdapter debugAdapter) {
45-
this.debugAdapter = debugAdapter;
47+
public DebugAdapterContext(Consumer<Messages.ProtocolMessage> messageConsumer, IProviderContext providerContext) {
48+
this.providerContext = providerContext;
49+
this.messageConsumer = messageConsumer;
4650
}
4751

4852
@Override
4953
public void sendEvent(DebugEvent event) {
50-
debugAdapter.sendEvent(event);
51-
}
52-
53-
@Override
54-
public void sendEventAsync(DebugEvent event) {
55-
debugAdapter.sendEventLater(event);
54+
messageConsumer.accept(new Messages.Event(event.type, event));
5655
}
5756

5857
@Override
5958
public <T extends IProvider> T getProvider(Class<T> clazz) {
60-
return debugAdapter.getProvider(clazz);
59+
return providerContext.getProvider(clazz);
6160
}
6261

6362
@Override

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

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

14+
import java.util.concurrent.CompletableFuture;
15+
1416
import com.microsoft.java.debug.core.protocol.Messages;
1517

1618
public interface IDebugAdapter {
17-
Messages.Response dispatchRequest(Messages.Request request);
19+
CompletableFuture<Messages.Response> dispatchRequest(Messages.Request request);
1820
}

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,13 @@
2020

2121
public interface IDebugAdapterContext {
2222
/**
23-
* Send debug event synchronously.
23+
* Send debug event to the DA.
2424
*
2525
* @param event
2626
* the debug event
2727
*/
2828
void sendEvent(Events.DebugEvent event);
2929

30-
/**
31-
* Send debug event asynchronously.
32-
*
33-
* @param event
34-
* the debug event
35-
*/
36-
void sendEventAsync(Events.DebugEvent event);
37-
3830
<T extends IProvider> T getProvider(Class<T> clazz);
3931

4032
/**

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
package com.microsoft.java.debug.core.adapter;
1313

1414
import java.util.List;
15+
import java.util.concurrent.CompletableFuture;
1516

16-
import com.microsoft.java.debug.core.protocol.Messages;
17+
import com.microsoft.java.debug.core.protocol.Messages.Response;
1718
import com.microsoft.java.debug.core.protocol.Requests;
19+
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
20+
import com.microsoft.java.debug.core.protocol.Requests.Command;
1821

1922
public interface IDebugRequestHandler {
2023
List<Requests.Command> getTargetCommands();
2124

22-
void handle(Requests.Command command, Requests.Arguments arguments, Messages.Response response, IDebugAdapterContext context);
25+
CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context);
2326

2427
}

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

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313

1414
import java.io.InputStream;
1515
import java.io.OutputStream;
16+
import java.util.logging.Level;
17+
import java.util.logging.Logger;
1618

19+
import com.microsoft.java.debug.core.Configuration;
1720
import com.microsoft.java.debug.core.UsageDataSession;
1821
import com.microsoft.java.debug.core.protocol.AbstractProtocolServer;
1922
import com.microsoft.java.debug.core.protocol.Messages;
2023

2124
public class ProtocolServer extends AbstractProtocolServer {
25+
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
26+
2227
private IDebugAdapter debugAdapter;
2328
private UsageDataSession usageDataSession = new UsageDataSession();
2429

@@ -33,42 +38,48 @@ public class ProtocolServer extends AbstractProtocolServer {
3338
*/
3439
public ProtocolServer(InputStream input, OutputStream output, IProviderContext context) {
3540
super(input, output);
36-
this.debugAdapter = new DebugAdapter((debugEvent, willSendLater) -> {
37-
// If the protocolServer has been stopped, it'll no longer receive any event.
38-
if (!terminateSession) {
39-
if (willSendLater) {
40-
sendEventLater(debugEvent.type, debugEvent);
41-
} else {
42-
sendEvent(debugEvent.type, debugEvent);
43-
}
44-
}
41+
this.debugAdapter = new DebugAdapter((message) -> {
42+
sendMessage(message);
4543
}, context);
4644
}
4745

4846
/**
4947
* A while-loop to parse input data and send output data constantly.
5048
*/
51-
public void start() {
49+
public void run() {
5250
usageDataSession.reportStart();
53-
super.start();
54-
}
55-
56-
/**
57-
* Sets terminateSession flag to true. And the dispatcher loop will be terminated after current dispatching operation finishes.
58-
*/
59-
public void stop() {
51+
super.run();
6052
usageDataSession.reportStop();
61-
super.stop();
6253
usageDataSession.submitUsageData();
6354
}
6455

65-
protected void dispatchRequest(Messages.Request request) {
66-
Messages.Response response = this.debugAdapter.dispatchRequest(request);
67-
if (request.command.equals("disconnect")) {
68-
stop();
56+
@Override
57+
protected void sendMessage(Messages.ProtocolMessage message) {
58+
super.sendMessage(message);
59+
if (message instanceof Messages.Response) {
60+
usageDataSession.recordResponse((Messages.Response) message);
61+
} else if (message instanceof Messages.Request) {
62+
usageDataSession.recordRequest((Messages.Request) message);
6963
}
70-
sendMessage(response);
71-
usageDataSession.recordResponse(response);
64+
}
65+
66+
protected void dispatchRequest(Messages.Request request) {
67+
usageDataSession.recordRequest(request);
68+
this.debugAdapter.dispatchRequest(request).whenComplete((response, ex) -> {
69+
if (response != null) {
70+
sendMessage(response);
71+
} else if (ex != null) {
72+
logger.log(Level.SEVERE, String.format("DebugSession dispatch exception: %s", ex.toString()), ex);
73+
sendMessage(AdapterUtils.setErrorResponse(response,
74+
ErrorCode.UNKNOWN_FAILURE,
75+
ex.getMessage() != null ? ex.getMessage() : ex.toString()));
76+
} else {
77+
logger.log(Level.SEVERE, "The request dispatcher should not return null response.");
78+
sendMessage(AdapterUtils.setErrorResponse(response,
79+
ErrorCode.UNKNOWN_FAILURE,
80+
"The request dispatcher should not return null response."));
81+
}
82+
});
7283
}
7384

7485
}

0 commit comments

Comments
 (0)