Skip to content

Commit 0eb8b3f

Browse files
Fail fast when the debug adapter's OnInitialize delegate throws
OmniSharp's `DebugAdapterServer.From()` (0.19.9) awaits an internal `AsyncSubject` that its `InitializeRequest` handler only signals on the success path. If an `OnInitialize` delegate throws, the handler faults before signaling, nothing errors the subject, and `From()` -- and thus `PsesDebugServer.StartAsync()` -- awaits it forever. So a startup failure wedges the entire debug server and rides the CI/job timeout instead of failing fast. This is a library limitation, not our bug: the same code is present on the library's `master`, so we can't fix it upstream without a fork or upgrade. #2328 fixes the specific trigger we hit on in-box Windows PowerShell (the `Get-ExecutionPolicy -List` type-data conflict), but the wedge mechanism is generic. Guard against any future `OnInitialize` failure: wrap the delegate body, log the exception, and signal `_serverStopped` so `WaitForShutdown` unblocks and the process exits cleanly. `Dispose`'s `_serverStopped` completion is now idempotent (`TrySetResult`) since the catch may have already completed it. This converts a silent multi-hour hang into a clean termination with a logged error -- the client sees the session end instead of waiting forever. See #2323. Drafted by Copilot (Claude Opus 4.8). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 71416ff commit 0eb8b3f

1 file changed

Lines changed: 35 additions & 16 deletions

File tree

src/PowerShellEditorServices/Server/PsesDebugServer.cs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.IO;
66
using System.Threading.Tasks;
77
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
89
using Microsoft.PowerShell.EditorServices.Handlers;
10+
using Microsoft.PowerShell.EditorServices.Logging;
911
using Microsoft.PowerShell.EditorServices.Services;
1012
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
1113
using OmniSharp.Extensions.DebugAdapter.Server;
@@ -89,23 +91,40 @@ public async Task StartAsync()
8991
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
9092
.OnInitialize(async (server, _, cancellationToken) =>
9193
{
92-
// Start the host if not already started, and enable debug mode (required
93-
// for remote debugging).
94-
//
95-
// TODO: We might need to fill in HostStartOptions here.
96-
_startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), cancellationToken).ConfigureAwait(false);
97-
_psesHost.DebugContext.EnableDebugMode();
98-
99-
// We need to give the host a handle to the DAP so it can register
100-
// notifications (specifically for sendKeyPress).
101-
if (_isTemp)
94+
try
10295
{
103-
_psesHost.DebugServer = server;
96+
// Start the host if not already started, and enable debug mode (required
97+
// for remote debugging).
98+
//
99+
// TODO: We might need to fill in HostStartOptions here.
100+
_startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), cancellationToken).ConfigureAwait(false);
101+
_psesHost.DebugContext.EnableDebugMode();
102+
103+
// We need to give the host a handle to the DAP so it can register
104+
// notifications (specifically for sendKeyPress).
105+
if (_isTemp)
106+
{
107+
_psesHost.DebugServer = server;
108+
}
109+
110+
// Clear any existing breakpoints before proceeding.
111+
BreakpointService breakpointService = server.GetService<BreakpointService>();
112+
await breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false);
113+
}
114+
catch (Exception e)
115+
{
116+
// Never let an exception escape this delegate. OmniSharp's
117+
// DebugAdapterServer only signals its internal initialize-complete
118+
// subject on the success path of the InitializeRequest handler, so a
119+
// throw here leaves DebugAdapterServer.From() (and thus StartAsync)
120+
// awaiting that subject forever -- wedging startup and riding the job
121+
// timeout instead of failing fast. Log the failure and signal shutdown
122+
// so WaitForShutdown unblocks and the process can exit cleanly.
123+
ServiceProvider.GetService<ILoggerFactory>()?
124+
.CreateLogger<PsesDebugServer>()
125+
.LogException("Failed to start the debug server; terminating the debug session.", e);
126+
_serverStopped.TrySetResult(true);
104127
}
105-
106-
// Clear any existing breakpoints before proceeding.
107-
BreakpointService breakpointService = server.GetService<BreakpointService>();
108-
await breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(false);
109128
})
110129
// The OnInitialized delegate gets run right before the server responds to the _Initialize_ request:
111130
// https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
@@ -134,7 +153,7 @@ public void Dispose()
134153
_debugAdapterServer?.Dispose();
135154
_inputStream.Dispose();
136155
_outputStream.Dispose();
137-
_serverStopped.SetResult(true);
156+
_serverStopped.TrySetResult(true);
138157
// TODO: If the debugger has stopped, should we clear the breakpoints?
139158
}
140159

0 commit comments

Comments
 (0)