Skip to content

Fix Http3 connection close metric sometimes remaining Unset#65398

Open
BrennanConroy wants to merge 1 commit intomainfrom
brecon/unset
Open

Fix Http3 connection close metric sometimes remaining Unset#65398
BrennanConroy wants to merge 1 commit intomainfrom
brecon/unset

Conversation

@BrennanConroy
Copy link
Member

Closes #57579

This is the same as what Http2 does:

catch (ConnectionResetException ex)
{
// Don't log ECONNRESET errors when there are no active streams on the connection. Browsers like IE will reset connections regularly.
if (_clientActiveStreamCount > 0)
{
Log.RequestProcessingError(ConnectionId, ex);
reason = ConnectionEndReason.ConnectionReset;
}
else
{
reason = ConnectionEndReason.TransportCompleted;
}

Copilot AI review requested due to automatic review settings February 11, 2026 22:25
@BrennanConroy BrennanConroy added feature-kestrel area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions labels Feb 11, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes an occasional debug-assert crash in Kestrel HTTP/3 metrics by ensuring an appropriate ConnectionEndReason is set when a ConnectionResetException occurs with no active requests (mirroring HTTP/2 behavior).

Changes:

  • Set ConnectionEndReason.TransportCompleted for HTTP/3 connection resets when _activeRequestCount == 0.
  • Add an HTTP/3 in-memory functional test that reproduces the “reset with no active requests” scenario to prevent regressions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs Ensures connection reset without active requests records TransportCompleted instead of leaving the reason Unset.
src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs Adds a regression test that triggers a connection reset early and validates metrics don’t report an error.

Comment on lines +902 to +920
public async Task ConnectionResetWithNoActiveRequests_ReportsTransportCompleted()
{
// When a ConnectionResetException occurs with no active requests (e.g., the client
// disconnects after all requests have completed), the connection end reason should be
// TransportCompleted, not Unset.

var abortTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

// skipCount: 0 means the first AcceptAsync throws ConnectionResetException immediately.
// The outbound control stream is created via ConnectAsync (not AcceptAsync) so it still succeeds.
Http3Api.MultiplexedConnectionContext = new ConnectionResetMultiplexedConnectionContext(Http3Api, skipCount: 0, abortTcs);
await Http3Api.InitializeConnectionAsync(_noopApplication);

// Wait for the connection to be aborted (ProcessRequestsAsync calls Abort which sets metrics
// before calling _multiplexedContext.Abort, which signals abortTcs).
await abortTcs.Task.DefaultTimeout();

MetricsAssert.NoError(Http3Api.ConnectionTags);
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

This test name/comment say it verifies the connection end reason is TransportCompleted, but the only assertion is MetricsAssert.NoError(...) (i.e., absence of error.type). That condition would also be true for other non-error end reasons, so the test isn’t actually validating the specific behavior it describes. Consider renaming the test/comment to match what’s asserted (e.g., that metrics don’t crash/aren’t left Unset), or add an assertion mechanism that can observe the exact ConnectionEndReason used.

Copilot uses AI. Check for mistakes.
Comment on lines +939 to +945
public override async ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default)
{
if (_skipCount-- <= 0)
{
throw new ConnectionResetException("Connection reset by peer.");
}
return await base.AcceptAsync(cancellationToken);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

AcceptAsync is marked async but it only forwards to base.AcceptAsync (or throws). This introduces an unnecessary async state machine in the hot path of the test harness. Consider removing async and returning the ValueTask from base.AcceptAsync(...) directly.

Suggested change
public override async ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default)
{
if (_skipCount-- <= 0)
{
throw new ConnectionResetException("Connection reset by peer.");
}
return await base.AcceptAsync(cancellationToken);
public override ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default)
{
if (_skipCount-- <= 0)
{
throw new ConnectionResetException("Connection reset by peer.");
}
return base.AcceptAsync(cancellationToken);

Copilot uses AI. Check for mistakes.
@adityamandaleeka
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@adityamandaleeka
Copy link
Member

LGTM other than the copilot nits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions feature-kestrel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kestrel Metrics can crash local test run

2 participants