Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 4 additions & 1 deletion src/Aspire.Hosting.Browsers/BrowserPageSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ public async ValueTask DisposeAsync()
try
{
var connection = _connection;
// The ReferenceEquals check is technically redundant today (connection was just read from _connection
// under the lock), but guards against future refactors that may read _connection earlier or release
// and re-acquire the lock before reaching this point.
if (connection is not null && ReferenceEquals(connection, _connection) && _targetId is not null)
{
try
Expand Down Expand Up @@ -416,7 +419,7 @@ private async Task<bool> TryReconnectAsync(Exception connectionError)

try
{
await Task.Delay(s_connectionRecoveryDelay, _stopCts.Token).ConfigureAwait(false);
await Task.Delay(s_connectionRecoveryDelay, _timeProvider, _stopCts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException) when (_stopCts.IsCancellationRequested)
{
Expand Down
56 changes: 56 additions & 0 deletions tests/Aspire.Hosting.Browsers.Tests/BrowserPageSessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,62 @@ await secondConnection.RaiseEventAsync(new BrowserLogsTargetDestroyedEvent(
await session.DisposeAsync();
}

[Fact]
public async Task MonitorAsync_CompletesWithConnectionLostWhenReconnectTimesOut()
{
var host = new TestBrowserHost();
var firstConnection = new FakeBrowserLogsCdpConnection
{
CreatedTargetId = "target-1",
AttachSessionId = "target-session-1"
};
var timeProvider = new FakeTimeProvider(new DateTimeOffset(2026, 4, 26, 0, 0, 0, TimeSpan.Zero));

// All reconnect attempts after the first connection will fail.
var reconnectAttempts = 0;
BrowserLogsCdpConnectionFactory connectionFactory = (eventHandler, logger, cancellationToken) =>
{
if (reconnectAttempts == 0)
{
reconnectAttempts++;
firstConnection.SetEventHandler(eventHandler);
return Task.FromResult<IBrowserLogsCdpConnection>(firstConnection);
}

reconnectAttempts++;
throw new InvalidOperationException("Simulated connection failure.");
};

var session = await BrowserPageSession.StartAsync(
host,
"session-0001",
new Uri("https://localhost:5001/"),
new BrowserConnectionDiagnosticsLogger("session-0001", NullLogger.Instance),
connectionFactory,
static _ => ValueTask.CompletedTask,
NullLogger<BrowserLogsSessionManager>.Instance,
timeProvider,
reuseInitialBlankTarget: false,
CancellationToken.None);

Assert.Equal("target-1", session.TargetId);

// Enable auto-advance AFTER startup so the reconnect deadline computation uses a time that is
// immediately exceeded by the next GetUtcNow() call in the while-loop condition check.
// This causes the recovery window to expire on the first iteration without needing to pump timers.
timeProvider.AutoAdvanceAmount = TimeSpan.FromSeconds(6);

Comment thread
mitchdenny marked this conversation as resolved.
Outdated
// Trigger connection loss to start the reconnect loop.
firstConnection.FailCompletion(new InvalidOperationException("Socket reset."));

var result = await session.Completion.DefaultTimeout();
Assert.Equal(BrowserPageSessionCompletionKind.ConnectionLost, result.CompletionKind);
Assert.NotNull(result.Error);
Assert.True(firstConnection.Disposed);

await session.DisposeAsync();
}

private static BrowserLogsCdpConnectionFactory CreateConnectionFactory(params FakeBrowserLogsCdpConnection[] connections)
{
var nextConnectionIndex = 0;
Expand Down
Loading