Skip to content

Commit 43e5211

Browse files
Add WS bridge over DAP TCP server (actions#4328)
Co-authored-by: Tingluo Huang <tingluohuang@github.com>
1 parent 4a587ad commit 43e5211

5 files changed

Lines changed: 1330 additions & 4 deletions

File tree

src/Runner.Worker/Dap/DapDebugger.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public sealed class DapDebugger : RunnerService, IDapDebugger
6666

6767
// Dev Tunnel relay host for remote debugging
6868
private TunnelRelayTunnelHost _tunnelRelayHost;
69+
private IWebSocketDapBridge _webSocketBridge;
6970

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

78+
// When true, skip the public websocket bridge and expose the raw DAP
79+
// listener directly on the configured tunnel port (unit tests only).
80+
internal bool SkipWebSocketBridge { get; set; }
81+
7782
// Synchronization for step execution
7883
private TaskCompletionSource<DapCommand> _commandTcs;
7984
private readonly object _stateLock = new object();
@@ -108,6 +113,7 @@ public sealed class DapDebugger : RunnerService, IDapDebugger
108113
_state == DapSessionState.Running;
109114

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

112118
public override void Initialize(IHostContext hostContext)
113119
{
@@ -133,9 +139,19 @@ public async Task StartAsync(IExecutionContext jobContext)
133139
_jobContext = jobContext;
134140
_readyTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
135141

136-
_listener = new TcpListener(IPAddress.Loopback, debuggerConfig.Tunnel.Port);
142+
var dapPort = SkipWebSocketBridge ? debuggerConfig.Tunnel.Port : 0;
143+
_listener = new TcpListener(IPAddress.Loopback, dapPort);
137144
_listener.Start();
138-
Trace.Info($"DAP debugger listening on {_listener.LocalEndpoint}");
145+
if (SkipWebSocketBridge)
146+
{
147+
Trace.Info($"DAP debugger listening on {_listener.LocalEndpoint}");
148+
}
149+
else
150+
{
151+
Trace.Info($"Internal DAP debugger listening on {_listener.LocalEndpoint}");
152+
_webSocketBridge = HostContext.CreateService<IWebSocketDapBridge>();
153+
_webSocketBridge.Start(debuggerConfig.Tunnel.Port, InternalDapPort);
154+
}
139155

140156
// Start Dev Tunnel relay so remote clients reach the local DAP port.
141157
// The relay is torn down explicitly in StopAsync (after the DAP session
@@ -274,6 +290,25 @@ public async Task StopAsync()
274290
_tunnelRelayHost = null;
275291
}
276292

293+
if (_webSocketBridge != null)
294+
{
295+
Trace.Info("Stopping WebSocket DAP bridge");
296+
var shutdownTask = _webSocketBridge.ShutdownAsync();
297+
if (await Task.WhenAny(shutdownTask, Task.Delay(5_000)) != shutdownTask)
298+
{
299+
Trace.Warning("WebSocket DAP bridge shutdown timed out after 5s");
300+
_ = shutdownTask.ContinueWith(
301+
t => Trace.Error($"WebSocket DAP bridge shutdown faulted: {t.Exception?.GetBaseException().Message}"),
302+
TaskContinuationOptions.OnlyOnFaulted);
303+
}
304+
else
305+
{
306+
Trace.Info("WebSocket DAP bridge stopped");
307+
}
308+
309+
_webSocketBridge = null;
310+
}
311+
277312
CleanupConnection();
278313

279314
// Cancel the connection loop first so AcceptTcpClientAsync unblocks
@@ -315,6 +350,7 @@ public async Task StopAsync()
315350
_connectionLoopTask = null;
316351
_loopCts?.Dispose();
317352
_loopCts = null;
353+
_webSocketBridge = null;
318354
}
319355

320356
public async Task OnStepStartingAsync(IStep step)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Threading.Tasks;
2+
using GitHub.Runner.Common;
3+
4+
namespace GitHub.Runner.Worker.Dap
5+
{
6+
[ServiceLocator(Default = typeof(WebSocketDapBridge))]
7+
public interface IWebSocketDapBridge : IRunnerService
8+
{
9+
void Start(int listenPort, int targetPort);
10+
Task ShutdownAsync();
11+
}
12+
}

0 commit comments

Comments
 (0)