diff --git a/src/Runner.Worker/Dap/DapDebugger.cs b/src/Runner.Worker/Dap/DapDebugger.cs index ec3adaf3e..5f5214dac 100644 --- a/src/Runner.Worker/Dap/DapDebugger.cs +++ b/src/Runner.Worker/Dap/DapDebugger.cs @@ -1320,6 +1320,13 @@ namespace GitHub.Runner.Worker.Dap _commandTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } + // If cancellation already fired before we created the new TCS, + // the registration callback targeted the old one. Unblock now. + if (cancellationToken.IsCancellationRequested) + { + _commandTcs.TrySetResult(DapCommand.Disconnect); + } + Trace.Info("Waiting for debugger command..."); var command = await _commandTcs.Task; diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 991e4b37b..bf451ce99 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -550,6 +550,21 @@ namespace GitHub.Runner.Worker } finally { + // If InitializeJob failed after the debugger was started, + // clean it up here since FinalizeJob won't run. + if (context.Result != null && _dapDebugger != null) + { + try + { + await _dapDebugger.OnJobCompletedAsync(); + } + catch (Exception ex) + { + Trace.Warning($"DAP debugger cleanup during failed init: {ex.Message}"); + } + _dapDebugger = null; + } + context.Debug("Finishing: Set up job"); context.Complete(); } diff --git a/src/Test/L0/Worker/DapDebuggerL0.cs b/src/Test/L0/Worker/DapDebuggerL0.cs index 9454b1a3a..2d1ac596b 100644 --- a/src/Test/L0/Worker/DapDebuggerL0.cs +++ b/src/Test/L0/Worker/DapDebuggerL0.cs @@ -744,14 +744,32 @@ namespace GitHub.Runner.Common.Tests.Worker await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5)); await waitTask; - // Complete the job — events are sent via OnJobCompletedAsync - await _debugger.OnJobCompletedAsync(); + // Complete the job — OnJobCompletedAsync pauses when stepping, + // so run it in the background and send continue to unblock. + var completedTask = _debugger.OnJobCompletedAsync(); - var msg1 = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5)); - var msg2 = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5)); + // Read the stopped event from the pause + var stoppedMsg = await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5)); + Assert.Contains("\"event\":\"stopped\"", stoppedMsg); - // Both events should arrive (order may vary) - var combined = msg1 + msg2; + // Send continue to unblock the pause + await SendRequestAsync(stream, new Request + { + Seq = 2, + Type = "request", + Command = "continue" + }); + + await completedTask; + + // Read remaining messages — continue response + continued event + terminated + exited + var allMessages = new System.Text.StringBuilder(); + for (int i = 0; i < 4; i++) + { + allMessages.Append(await ReadDapMessageAsync(stream, TimeSpan.FromSeconds(5))); + } + + var combined = allMessages.ToString(); Assert.Contains("\"event\":\"terminated\"", combined); Assert.Contains("\"event\":\"exited\"", combined); }