-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Update HttpSys shutdown logic #65217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
| Log.WaitingForRequestsToDrain(_logger, _outstandingRequests); | ||
| } | ||
|
|
||
| await _acceptLoopsCompleted.Task.ConfigureAwait(false); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this 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.StopAsyncto track and wait for accept loops to complete before disposing the listener, preventing invalid handle usage - Added
RequestQueue.StopProcessingRequestsmethod that callsHttpShutdownRequestQueueto gracefully cancel pending accept operations without closing the handle - Changed
MessagePump.Disposeto callStopAsyncwith 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() | ||
| { |
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
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.
| { | |
| { | |
| CheckDisposed(); |
|
|
||
| // Note: Response keys are validated in the ResponseTests | ||
| } | ||
| catch (Exception ex) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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>
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.