Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions src/Runner.Worker/Dap/DapDebugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public sealed class DapDebugger : RunnerService, IDapDebugger

// Dev Tunnel relay host for remote debugging
private TunnelRelayTunnelHost _tunnelRelayHost;
private WebSocketDapBridge _webSocketBridge;

// Cancellation source for the connection loop, cancelled in StopAsync
// so AcceptTcpClientAsync unblocks cleanly without relying on listener disposal.
Expand All @@ -74,6 +75,10 @@ public sealed class DapDebugger : RunnerService, IDapDebugger
// When true, skip tunnel relay startup (unit tests only)
internal bool SkipTunnelRelay { get; set; }

// When true, skip the public websocket bridge and expose the raw DAP
// listener directly on the configured tunnel port (unit tests only).
internal bool SkipWebSocketBridge { get; set; }

// Synchronization for step execution
private TaskCompletionSource<DapCommand> _commandTcs;
private readonly object _stateLock = new object();
Expand Down Expand Up @@ -108,6 +113,7 @@ public sealed class DapDebugger : RunnerService, IDapDebugger
_state == DapSessionState.Running;

internal DapSessionState State => _state;
internal int InternalDapPort => (_listener?.LocalEndpoint as IPEndPoint)?.Port ?? 0;

public override void Initialize(IHostContext hostContext)
{
Expand All @@ -133,9 +139,21 @@ public async Task StartAsync(IExecutionContext jobContext)
_jobContext = jobContext;
_readyTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

_listener = new TcpListener(IPAddress.Loopback, debuggerConfig.Tunnel.Port);
var dapPort = SkipWebSocketBridge ? debuggerConfig.Tunnel.Port : 0;
_listener = new TcpListener(IPAddress.Loopback, dapPort);
_listener.Start();
Trace.Info($"DAP debugger listening on {_listener.LocalEndpoint}");
if (SkipWebSocketBridge)
{
Trace.Info($"DAP debugger listening on {_listener.LocalEndpoint}");
}
else
{
Trace.Info($"Internal DAP debugger listening on {_listener.LocalEndpoint}");
_webSocketBridge = new WebSocketDapBridge();
_webSocketBridge.Initialize(HostContext);
_webSocketBridge.Configure(debuggerConfig.Tunnel.Port, InternalDapPort);
_webSocketBridge.Start();
}

// Start Dev Tunnel relay so remote clients reach the local DAP port.
// The relay is torn down explicitly in StopAsync (after the DAP session
Expand Down Expand Up @@ -274,6 +292,22 @@ public async Task StopAsync()
_tunnelRelayHost = null;
}

if (_webSocketBridge != null)
{
Trace.Info("Stopping WebSocket DAP bridge");
var shutdownTask = _webSocketBridge.ShutdownAsync();
if (await Task.WhenAny(shutdownTask, Task.Delay(5_000)) != shutdownTask)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small thing, if the 5s timeout fires shutdownTask is still running but we drop the reference and null the bridge. if it eventually faults that goes unobserved. might be nice to have a ContinueWith to log if it fails or something, just so we dont silently lose errors

{
Trace.Warning("WebSocket DAP bridge shutdown timed out after 5s");
}
else
{
Trace.Info("WebSocket DAP bridge stopped");
}

_webSocketBridge = null;
}

CleanupConnection();

// Cancel the connection loop first so AcceptTcpClientAsync unblocks
Expand Down Expand Up @@ -315,6 +349,7 @@ public async Task StopAsync()
_connectionLoopTask = null;
_loopCts?.Dispose();
_loopCts = null;
_webSocketBridge = null;
}

public async Task OnStepStartingAsync(IStep step)
Expand Down
Loading
Loading