You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
HttpClient will not send the HEADERS frame for requests that do have Content but do not have EndStream set when SendHeadersAsync is called. It appears the only case that this applies to may be AllowDuplex = true in a custom Content object.
It seems that SendHeadersAsync should always cause a flush as:
Causing a flush when headers are written appears to be happening for everything exceptAllowDuplex = true
Holding headers back seems like the wrong thing to do, ever, in that it will prevent the remote server from starting the request - delays here are bad
At the very least, requests that have AllowDuplex = true should also be added to the mustFlush list
Actual behavior
The behavior is changed by subsequent requests sent on the same connection:
Subsequent AllowDuplex = true requests can cause a flush if there are enough parallel requests sent AND they have enough headers set (the values may need to be unique to avoid deduplication)
Under the right conditions this can make it appear that the problem does not exist
Subsequent requests that set ExpectContinue will cause a flush
Setting ExpectContinue will always cause this to work, but results in an additional round trip (confirm?) and is not desirable
Requests that either have no Content or that have EndStream set (essentially: AllowDuplex = false) will cause a flush
Sending a GET request (or POST with ready-at-send Content) will cause the stuck request to send its HEADERS frame
The HEADERS frame will be delayed up to the time that the GET request is sent
Failure to use one of the above workarounds will cause the request to timeout without ever sending the HEADERS frame.
This issue says to support bidirectional content over Http2
The support is nearly 100% there, this was the only issue I discovered in hundreds of millions of requests that read the response body (to completion) before sending the request body
My project was observing periodic 5 second hangs that corresponded to a 5 second interval of a "ping" GET request sent over the same HttpClient
Known Workarounds
See actual behavior, but it appears that ExpectContinue = true is a workaround, as is sending a ton of headers.
Configuration
dotnet 8.0 / also release/8.0 branch built locally as of 2023-12-20
Mac OS X Sonoma
ARM64
The problem does not appear to be specific to this configuration at all
Other information
Possibly the simplest fix is that the callback in SendHeadersAsync should always return true, causing a flush. The nominal case of non-duplex requests essentially always causes this to return true and it seems only this odd case of duplex requests is causing it to return false.
Description
HttpClient will not send the
HEADERSframe for requests that do haveContentbut do not haveEndStreamset whenSendHeadersAsyncis called. It appears the only case that this applies to may beAllowDuplex = truein a customContentobject.Reproduction Steps
Complete example code:
https://github.com/huntharo/httpclient-duplex-deadlock
Video demonstrating the issue and the fix:
Expected behavior
It seems that
SendHeadersAsyncshould always cause a flush as:AllowDuplex = trueAllowDuplex = trueshould also be added to themustFlushlistActual behavior
The behavior is changed by subsequent requests sent on the same connection:
AllowDuplex = truerequests can cause a flush if there are enough parallel requests sent AND they have enough headers set (the values may need to be unique to avoid deduplication)ExpectContinuewill cause a flushContentor that haveEndStreamset (essentially:AllowDuplex = false) will cause a flushContent) will cause the stuck request to send itsHEADERSframeHEADERSframe will be delayed up to the time that the GET request is sentFailure to use one of the above workarounds will cause the request to timeout without ever sending the
HEADERSframe.Regression?
Known Workarounds
See actual behavior, but it appears that
ExpectContinue = trueis a workaround, as is sending a ton of headers.Configuration
release/8.0branch built locally as of 2023-12-20Other information
Possibly the simplest fix is that the callback in
SendHeadersAsyncshould always returntrue, causing a flush. The nominal case of non-duplex requests essentially always causes this to returntrueand it seems only this odd case of duplex requests is causing it to returnfalse.runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs
Line 1684 in 2f1fbb0
However, a more narrow fix is to add in
mustFlush: ... || duplexhere after moving thebool duplex =line to be above theSendHeadersAsynccall:runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs
Line 1999 in 2f1fbb0
I would love to make the PR and add tests for this after the approach is approved.