-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
SocketsHttpHandler should proactively handle EOF from server on idle HTTP/1.1 connections #60729
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsFrom RFC 2616:
SocketsHttpHandler does not do this currently. We do issue a "read-ahead" read on idle connections, and we do close the connection during scavenging if we detect (via the read-ahead) that the server has closed the connection. But scavenging only happens periodically. We could instead close the connection immediately when the read-ahead completes. This seems more in line with the spirit of the RFC above.
|
We used to not do this as the read ahead read valuetask was directly consumed by the http receive code, it could only have a single consumer, and we didn't want the overhead of creating multiple additional tasks and continuations to implement this. But now that we structured the read differently, already paying an extra state machine to be able to do a zero-byte read, it should be trivial to add this logic into ReadAheadWithZeroByteReadAsync, just checking the actual number of bytes read from the second actual read before returning it. |
Aside from the benefits stated above -- that is, being more RFC compliant and playing nicer with servers -- this would also allow us to close these dead connections more promptly, which would reduce resource utilization overall (including the kernel resources for the socket itself). That seems like a nice benefit. I wonder if we could/should handle connection idle timeout and lifetime in a similar way. That is, we'd managed timers for these per-connection and then if the timer expires, remove the connection from the pool. This might add some per-connection overhead, but perhaps we could make it small in practice. For example, defer timer creation until it's actually needed, share a timer for both idle and lifetime (we only really need one for whichever will expire first), even pool timers if we need to. This would have a couple advantages: (1) Get rid of (most of) the scavenging logic... we would still need to clean up pools themselves when they become unused, but this is a small fraction of the current logic, and doesn't need to happen frequently. This would also help reduce contention on the connection pool lock. Additionally, for idle timeout specifically: Currently we don't check idle timeout when we retrieve an idle connection from the pool; we only check during scavenging. This means our idle timeout is not very exact in practice; the scavenge period is 1/4 of the idle timeout period, so the "effective" idle timeout is actually somewhere between the 1 to 1.25 times the specified idle timeout. This may be surprising to customers, especially if they are trying to use the idle timeout to avoid issues that can occur when the server decides to idle timeout the connection before the client does. (We could of course enforce idle timeout strictly when we retrieve an idle connection from the pool -- in fact we used to do this, but removed it. But the above approach would provide the same behavior as well as the other benefits above.) The implementation details here would be a bit different for HTTP/1.1 and HTTP2, but they could probably share some logic. And for better or worse, we have not yet implemented scavenging for HTTP3 connections. See #54968. |
For anyone working on this in the future, this is the sort of test we may expect to pass with this change: https://gist.github.com/MihaZupan/2e616b1378f31560a986b42ee9d98ce5 |
From RFC 2616:
SocketsHttpHandler does not do this currently. We do issue a "read-ahead" read on idle connections, and we do close the connection during scavenging if we detect (via the read-ahead) that the server has closed the connection. But scavenging only happens periodically.
We could instead close the connection immediately when the read-ahead completes (and the connection is not in use). This seems more in line with the spirit of the RFC above.
The text was updated successfully, but these errors were encountered: