Skip to content

Fix LongPolling Teardown Race on Forbidden Cleanup DELETE - #68191

Draft
vendasankarsf3945 wants to merge 3 commits into
dotnet:mainfrom
vendasankarsf3945:68149-longpolling-forbidden-delete
Draft

Fix LongPolling Teardown Race on Forbidden Cleanup DELETE#68191
vendasankarsf3945 wants to merge 3 commits into
dotnet:mainfrom
vendasankarsf3945:68149-longpolling-forbidden-delete

Conversation

@vendasankarsf3945

Copy link
Copy Markdown
Contributor

Fix LongPolling Teardown Race on Forbidden Cleanup DELETE

Description

This PR fixes a flaky teardown race in HubConnectionTests.RefreshChangingUserIdentifierClosesConnection that caused intermittent test failures in the LongPolling transport variant.

When an authentication refresh changes the SignalR UserIdentifier, the server immediately closes the connection. During client shutdown, the LongPolling transport sends a cleanup DELETE request to the server endpoint. Due to the race between server-side connection invalidation and client-side teardown, the server can return 403 Forbidden on that DELETE. Previously, only 404 Not Found was treated as a benign "already closed" response — any other non-success status, including 403, would surface as an HttpRequestException and fail the test teardown even though the connection had been closed correctly.

The fix extends SendDeleteRequest in LongPollingTransport to also treat 403 Forbidden as a graceful already-closed outcome, and removes the [QuarantinedTest] attribute from the now-stable test.

Validation / Investigation

  1. The failure was analyzed and determined to occur only in the LongPolling transport variant of the RefreshChangingUserIdentifierClosesConnection theory — WebSockets and ServerSentEvents are not affected.
  2. When the server aborts a connection because the refreshed principal maps to a different UserIdentifier, the long-polling session is invalidated server-side before the client has a chance to send its cleanup DELETE.
  3. The server correctly returns 403 Forbidden for the stale DELETE request; the client was not handling this response code gracefully.
  4. The test itself was passing (the connection was correctly closed on user identifier change), but the teardown exception propagated from VerifyNoErrorsScope.Dispose() into InProcessTestServer.DisposeAsync(), causing a false failure.
  5. The test was run 100 consecutive times after the fix and passed every time.

Changes

  1. Extended SendDeleteRequest in LongPollingTransport.cs to treat 403 Forbidden — in addition to 404 Not Found — as a benign "connection already closed" response.
  2. Added a unit test LongPollingTransportTreatsForbiddenDeleteAsAlreadyClosed to LongPollingTransportTests.cs to lock in this behavior.
  3. Removed the [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/68149")] attribute from RefreshChangingUserIdentifierClosesConnection.

Fixes #68149.

Copilot AI review requested due to automatic review settings August 4, 2026 07:58
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Aug 4, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Thanks for your PR, @vendasankarsf3945. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This pull request stabilizes SignalR client LongPolling test teardown by treating 403 Forbidden responses to the LongPolling cleanup DELETE as a benign “already closed” outcome (similar to the existing 404 NotFound handling), and removes test quarantine now that the scenario is reliable.

Changes:

  • Updated LongPollingTransport.SendDeleteRequest to treat 403 Forbidden as an already-closed cleanup response.
  • Added a unit test validating that 403 during cleanup DELETE does not surface as a failure.
  • Removed the quarantine attribute from RefreshChangingUserIdentifierClosesConnection.

Reviewed changes

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

File Description
src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/LongPollingTransport.cs Extends benign cleanup handling to include 403 Forbidden during teardown DELETE.
src/SignalR/clients/csharp/Client/test/UnitTests/LongPollingTransportTests.cs Adds coverage ensuring 403 cleanup DELETE is treated as already-closed and does not fail Stop/teardown.
src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.AuthenticationRefresh.cs Removes [QuarantinedTest] from the stabilized auth-refresh/user-identifier test.

Comment on lines 233 to +237
var request = new HttpRequestMessage(HttpMethod.Delete, url);

var response = await _httpClient.SendAsync(request).ConfigureAwait(false);

if (response.StatusCode == HttpStatusCode.NotFound)
if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidden)
Comment on lines +237 to 240
if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidden)
{
Log.ConnectionAlreadyClosedSendingDeleteRequest(_logger, url);
}
@BrennanConroy

Copy link
Copy Markdown
Member

Not sure we care about hiding 403. Fixing the test in #68096

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

Labels

community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Quarantine HubConnectionTests.RefreshChangingUserIdentifierClosesConnection

3 participants