1515import java .io .OutputStream ;
1616import java .util .concurrent .CompletableFuture ;
1717import java .util .concurrent .CompletionException ;
18+ import java .util .concurrent .ConcurrentLinkedQueue ;
1819import java .util .logging .Level ;
1920import java .util .logging .Logger ;
2021
2122import com .microsoft .java .debug .core .Configuration ;
2223import com .microsoft .java .debug .core .DebugException ;
2324import com .microsoft .java .debug .core .UsageDataSession ;
2425import com .microsoft .java .debug .core .protocol .AbstractProtocolServer ;
26+ import com .microsoft .java .debug .core .protocol .Events .DebugEvent ;
27+ import com .microsoft .java .debug .core .protocol .Events .StoppedEvent ;
2528import com .microsoft .java .debug .core .protocol .Messages ;
2629import com .sun .jdi .VMDisconnectedException ;
2730
@@ -30,6 +33,8 @@ public class ProtocolServer extends AbstractProtocolServer {
3033
3134 private IDebugAdapter debugAdapter ;
3235 private UsageDataSession usageDataSession = new UsageDataSession ();
36+ private boolean isDispatchingRequest = false ;
37+ private ConcurrentLinkedQueue <DebugEvent > eventQueue = new ConcurrentLinkedQueue <>();
3338
3439 /**
3540 * Constructs a protocol server instance based on the given input stream and output stream.
@@ -75,43 +80,87 @@ public CompletableFuture<Messages.Response> sendRequest(Messages.Request request
7580 }
7681
7782 @ Override
78- protected void dispatchRequest (Messages .Request request ) {
79- usageDataSession .recordRequest (request );
80- debugAdapter .dispatchRequest (request ).thenCompose ((response ) -> {
81- CompletableFuture <Void > future = new CompletableFuture <>();
82- if (response != null ) {
83- sendResponse (response );
84- future .complete (null );
83+ public void sendEvent (DebugEvent event ) {
84+ // See the two bugs https://github.com/Microsoft/java-debug/issues/134 and https://github.com/Microsoft/vscode/issues/58327,
85+ // it requires the java-debug to send the StoppedEvent after ContinueResponse/StepResponse is received by DA.
86+ if (event instanceof StoppedEvent ) {
87+ sendEventLater (event );
88+ } else {
89+ super .sendEvent (event );
90+ }
91+
92+ }
93+
94+ /**
95+ * If the the dispatcher is idle, then send the event to the DA immediately.
96+ * Else add the new event to an eventQueue first and send them when dispatcher becomes idle again.
97+ * @param eventType
98+ * event type
99+ * @param body
100+ * event content
101+ */
102+ private void sendEventLater (DebugEvent event ) {
103+ synchronized (this ) {
104+ if (this .isDispatchingRequest ) {
105+ this .eventQueue .offer (event );
85106 } else {
86- future .completeExceptionally (new DebugException ("The request dispatcher should not return null response." ,
87- ErrorCode .UNKNOWN_FAILURE .getId ()));
107+ super .sendEvent (event );
88108 }
89- return future ;
90- }).exceptionally ((ex ) -> {
91- Messages .Response response = new Messages .Response (request .seq , request .command );
92- if (ex instanceof CompletionException && ex .getCause () != null ) {
93- ex = ex .getCause ();
109+ }
110+ }
111+
112+ @ Override
113+ protected void dispatchRequest (Messages .Request request ) {
114+ usageDataSession .recordRequest (request );
115+ try {
116+ synchronized (this ) {
117+ this .isDispatchingRequest = true ;
94118 }
95119
96- if (ex instanceof VMDisconnectedException ) {
97- // mark it success to avoid reporting error on VSCode.
98- response .success = true ;
99- sendResponse (response );
100- } else {
101- String exceptionMessage = ex .getMessage () != null ? ex .getMessage () : ex .toString ();
102- ErrorCode errorCode = ex instanceof DebugException ? ErrorCode .parse (((DebugException ) ex ).getErrorCode ()) : ErrorCode .UNKNOWN_FAILURE ;
103- boolean isUserError = ex instanceof DebugException && ((DebugException ) ex ).isUserError ();
104- if (isUserError ) {
105- usageDataSession .recordUserError (errorCode );
120+ debugAdapter .dispatchRequest (request ).thenCompose ((response ) -> {
121+ CompletableFuture <Void > future = new CompletableFuture <>();
122+ if (response != null ) {
123+ sendResponse (response );
124+ future .complete (null );
106125 } else {
107- logger .log (Level .SEVERE , String .format ("[error response][%s]: %s" , request .command , exceptionMessage ), ex );
126+ future .completeExceptionally (new DebugException ("The request dispatcher should not return null response." ,
127+ ErrorCode .UNKNOWN_FAILURE .getId ()));
108128 }
129+ return future ;
130+ }).exceptionally ((ex ) -> {
131+ Messages .Response response = new Messages .Response (request .seq , request .command );
132+ if (ex instanceof CompletionException && ex .getCause () != null ) {
133+ ex = ex .getCause ();
134+ }
135+
136+ if (ex instanceof VMDisconnectedException ) {
137+ // mark it success to avoid reporting error on VSCode.
138+ response .success = true ;
139+ sendResponse (response );
140+ } else {
141+ String exceptionMessage = ex .getMessage () != null ? ex .getMessage () : ex .toString ();
142+ ErrorCode errorCode = ex instanceof DebugException ? ErrorCode .parse (((DebugException ) ex ).getErrorCode ()) : ErrorCode .UNKNOWN_FAILURE ;
143+ boolean isUserError = ex instanceof DebugException && ((DebugException ) ex ).isUserError ();
144+ if (isUserError ) {
145+ usageDataSession .recordUserError (errorCode );
146+ } else {
147+ logger .log (Level .SEVERE , String .format ("[error response][%s]: %s" , request .command , exceptionMessage ), ex );
148+ }
149+
150+ sendResponse (AdapterUtils .setErrorResponse (response ,
151+ errorCode ,
152+ exceptionMessage ));
153+ }
154+ return null ;
155+ }).join ();
156+ } finally {
157+ synchronized (this ) {
158+ this .isDispatchingRequest = false ;
159+ }
109160
110- sendResponse (AdapterUtils .setErrorResponse (response ,
111- errorCode ,
112- exceptionMessage ));
161+ while (this .eventQueue .peek () != null ) {
162+ super .sendEvent (this .eventQueue .poll ());
113163 }
114- return null ;
115- }).join ();
164+ }
116165 }
117166}
0 commit comments