Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
40 changes: 38 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,22 @@ 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(
HostContext.GetTrace("DapWebSocketBridge"),
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 +293,22 @@ public async Task StopAsync()
_tunnelRelayHost = null;
}

if (_webSocketBridge != null)
{
Trace.Info("Stopping WebSocket DAP bridge");
var disposeTask = _webSocketBridge.DisposeAsync().AsTask();
if (await Task.WhenAny(disposeTask, Task.Delay(5_000)) != disposeTask)
{
Trace.Warning("WebSocket DAP bridge dispose 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 +350,7 @@ public async Task StopAsync()
_connectionLoopTask = null;
_loopCts?.Dispose();
_loopCts = null;
_webSocketBridge = null;
}

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