Skip to content

Commit e4408b8

Browse files
authored
chore: fix TestHttpServer test flake (#159)
Reworks the TestHttpServer threading to: - Use an async method instead (so we can wait with a cancellation token) - Use a cancellation token while awaiting the listener to return a request - `GetContext()` doesn't take a CTS directly, so we call `Task.WaitAsync(CancellationToken ct)` instead - Adds some exception handling and discarding where necessary to ignore expected errors during shutdown Closes coder/internal#603
1 parent c1ea64d commit e4408b8

File tree

1 file changed

+31
-17
lines changed

1 file changed

+31
-17
lines changed

Tests.Vpn.Service/TestHttpServer.cs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class TestHttpServer : IDisposable
1313
private readonly CancellationTokenSource _cts = new();
1414
private readonly Func<HttpListenerContext, Task> _handler;
1515
private readonly HttpListener _listener;
16-
private readonly Thread _listenerThread;
16+
private readonly Task _listenerTask;
1717

1818
public string BaseUrl { get; private set; }
1919

@@ -60,31 +60,45 @@ public TestHttpServer(Func<HttpListenerContext, Task> handler)
6060
throw new InvalidOperationException("Could not find a free port to listen on");
6161
BaseUrl = $"http://localhost:{port}";
6262

63-
_listenerThread = new Thread(() =>
64-
{
65-
while (!_cts.Token.IsCancellationRequested)
66-
try
67-
{
68-
var context = _listener.GetContext();
69-
Task.Run(() => HandleRequest(context));
70-
}
71-
catch (HttpListenerException) when (_cts.Token.IsCancellationRequested)
72-
{
73-
break;
74-
}
75-
});
76-
77-
_listenerThread.Start();
63+
_listenerTask = RequestLoop();
7864
}
7965

8066
public void Dispose()
8167
{
8268
_cts.Cancel();
8369
_listener.Stop();
84-
_listenerThread.Join();
70+
try
71+
{
72+
_listenerTask.GetAwaiter().GetResult();
73+
}
74+
catch (TaskCanceledException)
75+
{
76+
// Ignore
77+
}
8578
GC.SuppressFinalize(this);
8679
}
8780

81+
private async Task RequestLoop()
82+
{
83+
while (!_cts.Token.IsCancellationRequested)
84+
try
85+
{
86+
var contextTask = _listener.GetContextAsync();
87+
// Wait with a cancellation token.
88+
await contextTask.WaitAsync(_cts.Token);
89+
// Get the context or throw if there was an error.
90+
var context = await contextTask;
91+
// Run the handler in the background.
92+
_ = Task.Run(() => HandleRequest(context));
93+
}
94+
catch (HttpListenerException) when (_cts.Token.IsCancellationRequested)
95+
{
96+
// Ignore, we expect the listener to throw an exception when
97+
// it's stopped
98+
break;
99+
}
100+
}
101+
88102
private async Task HandleRequest(HttpListenerContext context)
89103
{
90104
try

0 commit comments

Comments
 (0)