Skip to content

Conversation

@BrennanConroy
Copy link
Member

Updated HttpSys's shutdown logic to be more like Kestrels.
IServer.Dispose calls IServer.StopAsync with an already canceled token.
StopAsync now waits for the accept loops to complete, before it only waited for active requests to finish which left open a race where we dispose the request queue handle while the accept loops were still active.

In the ungraceful close case, we still wait for the accept loops to complete, but don't wait for any blocked request processing to complete (imagine app logic taking a long time to finish). The only case where this would be slightly problematic is when using UnsafePreferInlineScheduling and app code was holding onto request processing, then the accept loop wouldn't complete and shutdown would hang until the app code completed.

This should hopefully reduce/eliminate problems we've seen in test code with handles being invalid when they shouldn't be.

@BrennanConroy

This comment was marked as outdated.

@azure-pipelines

This comment was marked as outdated.

@BrennanConroy

This comment was marked as outdated.

@azure-pipelines

This comment was marked as outdated.

@BrennanConroy

This comment was marked as outdated.

@azure-pipelines

This comment was marked as outdated.

@BrennanConroy

This comment was marked as outdated.

@azure-pipelines

This comment was marked as outdated.

@BrennanConroy

This comment was marked as outdated.

@azure-pipelines

This comment was marked as outdated.

Log.WaitingForRequestsToDrain(_logger, _outstandingRequests);
}

await _acceptLoopsCompleted.Task.ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

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

Do we really want to wait for the accept loops in the abortive new CancellationToken(canceled: true) case?

Copy link
Member Author

@BrennanConroy BrennanConroy Jan 26, 2026

Choose a reason for hiding this comment

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

I think so. In normal cases (not using inline scheduling) they should be very quick to complete as they just call HttpReceiveHttpRequest which either returns with a request immediately which we dispatch off the accept loop, goes async which gets off the accept loop, or errors due to us closing the request queue.

Edit: I also think Kestrel does this too, waits for its accept loops to finish.

@BrennanConroy BrennanConroy marked this pull request as ready for review January 26, 2026 22:31
Copilot AI review requested due to automatic review settings January 26, 2026 22:31
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

This PR refactors HttpSys's shutdown logic to align with Kestrel's approach. The key improvement is ensuring that StopAsync waits for accept loops to complete before disposing resources, eliminating a race condition where the request queue handle could be disposed while accept loops were still active.

Changes:

  • Modified MessagePump.StopAsync to track and wait for accept loops to complete before disposing the listener, preventing invalid handle usage
  • Added RequestQueue.StopProcessingRequests method that calls HttpShutdownRequestQueue to gracefully cancel pending accept operations without closing the handle
  • Changed MessagePump.Dispose to call StopAsync with an already-cancelled token for ungraceful shutdown
  • Updated test utilities and test expectations to accommodate the new shutdown behavior where cancelled shutdowns abort in-flight requests

Reviewed changes

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

Show a summary per file
File Description
src/Servers/HttpSys/src/MessagePump.cs Implements accept loop tracking with counters and completion sources, refactors StopAsync to wait for accept loops, and updates Dispose to use StopAsync
src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs Adds StopProcessingRequests method calling HttpShutdownRequestQueue, updates Dispose to use SetHandleAsInvalid instead of Dispose
src/Servers/HttpSys/src/NativeMethods.txt Adds HttpShutdownRequestQueue native method declaration
src/Servers/HttpSys/test/FunctionalTests/Utilities.cs Adds configureOptions parameter to CreateHttpServerReturnRoot for test flexibility
src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs Updates test expectations from successful responses to expecting exceptions when shutdown is cancelled
src/Servers/HttpSys/test/FunctionalTests/RequestTests.cs Updates all test calls to pass configureOptions parameter, simplifies one test by removing try-catch, adds optimization comment for MaxAccepts setting

/// to be properly cleaned up before disposing the BoundHandle.
/// </summary>
internal void StopProcessingRequests()
{
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The StopProcessingRequests method should check if the request queue is already disposed before calling HttpShutdownRequestQueue. Without this check, calling this method on a disposed instance could result in operating on an invalid handle. Consider adding CheckDisposed() at the beginning of this method, similar to how SetLengthLimit and SetRejectionVerbosity handle disposed state.

Suggested change
{
{
CheckDisposed();

Copilot uses AI. Check for mistakes.

// Note: Response keys are validated in the ResponseTests
}
catch (Exception ex)
Copy link
Member

Choose a reason for hiding this comment

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

why are we not interested in reporting error here anymore?

Copy link
Member Author

Choose a reason for hiding this comment

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

Before, the Assert.Equal(string.Empty, response) check would truncate the error so all you'd see is something like "XUnitAssertException". Now SendRequestAsync will throw with the XUnit exception which will let us see the actual error.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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-httpsys

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants