Closes #36216
Follow up to #36123, which implemented a read-side throttle so the server won't drain the socket buffer into application memory without bound. There was a tiny window left unaddressed which allowed one additional read operation of up to 65kB from the socket buffer in rare cases, correctly failing the test.
The original throttle works by only registering socket recv events when EITHER:
- A pending request is being built from socket data (
m_req != nullptr) - The receive buffer
m_recv_bufferis empty.
The idea is "don't read from the socket unless we need to, in order to complete an incoming request".
The new rule applied in this PR is "if a request is already busy in a worker thread, don't read at all".
The test sends a blocking HTTP request followed by a flood of data, expecting that flood to be blocked at some point within a few seconds by TCP backpressure: the application stops reading from the socket and then the kernel stops reading from the client (BlockingIOError in the python test).
The TCP spec requires clients to periodically probe the server to learn when the window has been reopened: https://www.rfc-editor.org/rfc/rfc9293.html#name-zero-window-probing
Because of the bug in the throttle logic from #36123, this probe would discover a non-zero window and send more data, resetting the stall timer in the test. This may repeat until the socket buffer is full again (the kernel replaced the 65kB the application foolishly drained) at which point the stream is truly blocked.
On linux platforms this probe is sent around 200ms, so even after a few cycles the server throttle would still register as solid to the test.
On macos however, given the small size of the open window, the probe is delayed 5 seconds. The cycle repeating every 5 seconds breaks the assumptions in the test and is interpreted as no throttle at all.
Note that the worst-case scenario for this bug is only 65kB so this is not a critical OOM fix.
I spent a few days with GPT, kimi, Sonnet and Opus trying to improve the test, or make it fail more reliably on master with no satisfying outcome. Tweaking the STALL_TIMEOUT constant, for example, either makes the test less sensitive to regression or more likely to fail on macos. I think we are at the limit of what we can test from the client side. We could add more DebugLog messages to the server and test server-side behavior a bit more like the other throttle test check_slow_read_throttle() but I chose to leave that alone for now.