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
@@ -31,6 +34,10 @@ public class ProtocolServer extends AbstractProtocolServer {
3134 private IDebugAdapter debugAdapter ;
3235 private UsageDataSession usageDataSession = new UsageDataSession ();
3336
37+ private Object lock = new Object ();
38+ private boolean isDispatchingRequest = false ;
39+ private ConcurrentLinkedQueue <DebugEvent > eventQueue = new ConcurrentLinkedQueue <>();
40+
3441 /**
3542 * Constructs a protocol server instance based on the given input stream and output stream.
3643 * @param input
@@ -75,43 +82,83 @@ public CompletableFuture<Messages.Response> sendRequest(Messages.Request request
7582 }
7683
7784 @ 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 );
85+ public void sendEvent (DebugEvent event ) {
86+ // See the two bugs https://github.com/Microsoft/java-debug/issues/134 and https://github.com/Microsoft/vscode/issues/58327,
87+ // it requires the java-debug to send the StoppedEvent after ContinueResponse/StepResponse is received by DA.
88+ if (event instanceof StoppedEvent ) {
89+ sendEventLater (event );
90+ } else {
91+ super .sendEvent (event );
92+ }
93+
94+ }
95+
96+ /**
97+ * If the the dispatcher is idle, then send the event to the DA immediately.
98+ * Else add the new event to an eventQueue first and send them when dispatcher becomes idle again.
99+ */
100+ private void sendEventLater (DebugEvent event ) {
101+ synchronized (lock ) {
102+ if (this .isDispatchingRequest ) {
103+ this .eventQueue .offer (event );
85104 } else {
86- future .completeExceptionally (new DebugException ("The request dispatcher should not return null response." ,
87- ErrorCode .UNKNOWN_FAILURE .getId ()));
105+ super .sendEvent (event );
88106 }
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 ();
107+ }
108+ }
109+
110+ @ Override
111+ protected void dispatchRequest (Messages .Request request ) {
112+ usageDataSession .recordRequest (request );
113+ try {
114+ synchronized (lock ) {
115+ this .isDispatchingRequest = true ;
94116 }
95117
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 );
118+ debugAdapter .dispatchRequest (request ).thenCompose ((response ) -> {
119+ CompletableFuture <Void > future = new CompletableFuture <>();
120+ if (response != null ) {
121+ sendResponse (response );
122+ future .complete (null );
123+ } else {
124+ future .completeExceptionally (new DebugException ("The request dispatcher should not return null response." ,
125+ ErrorCode .UNKNOWN_FAILURE .getId ()));
126+ }
127+ return future ;
128+ }).exceptionally ((ex ) -> {
129+ Messages .Response response = new Messages .Response (request .seq , request .command );
130+ if (ex instanceof CompletionException && ex .getCause () != null ) {
131+ ex = ex .getCause ();
132+ }
133+
134+ if (ex instanceof VMDisconnectedException ) {
135+ // mark it success to avoid reporting error on VSCode.
136+ response .success = true ;
137+ sendResponse (response );
106138 } else {
107- logger .log (Level .SEVERE , String .format ("[error response][%s]: %s" , request .command , exceptionMessage ), ex );
139+ String exceptionMessage = ex .getMessage () != null ? ex .getMessage () : ex .toString ();
140+ ErrorCode errorCode = ex instanceof DebugException ? ErrorCode .parse (((DebugException ) ex ).getErrorCode ()) : ErrorCode .UNKNOWN_FAILURE ;
141+ boolean isUserError = ex instanceof DebugException && ((DebugException ) ex ).isUserError ();
142+ if (isUserError ) {
143+ usageDataSession .recordUserError (errorCode );
144+ } else {
145+ logger .log (Level .SEVERE , String .format ("[error response][%s]: %s" , request .command , exceptionMessage ), ex );
146+ }
147+
148+ sendResponse (AdapterUtils .setErrorResponse (response ,
149+ errorCode ,
150+ exceptionMessage ));
108151 }
152+ return null ;
153+ }).join ();
154+ } finally {
155+ synchronized (lock ) {
156+ this .isDispatchingRequest = false ;
157+ }
109158
110- sendResponse (AdapterUtils .setErrorResponse (response ,
111- errorCode ,
112- exceptionMessage ));
159+ while (this .eventQueue .peek () != null ) {
160+ super .sendEvent (this .eventQueue .poll ());
113161 }
114- return null ;
115- }).join ();
162+ }
116163 }
117164}
0 commit comments