#35780 http: linger-close after parse errors so clients can read the reply
https://github.com/bitcoin/bitcoin/pull/35780 · · +139/-21 in 8 files, 2 commits · labels: RPC/REST/ZMQ, Needs rebase · draft
Goal
- Ensure HTTP clients receive proper error responses instead of connection aborts on parse failures
- Fixes intermittent functional test failures and ungraceful connection resets on Windows
Implements a lingering close mechanism in the HTTP server when rejecting requests during parsing (such as HTTP 400 Bad Request or HTTP 413 Content Too Large). It adds a Sock::ShutdownSend() wrapper to half-close the send side after flushing the error response, allowing remaining unread inbound data to be drained up to a 1-second timeout before terminating the connection.
Problem: When the HTTP server encounters a parse or size error, closing the socket immediately while unread data remains in the operating system receive buffer triggers a TCP RST on Windows (issue #35632). This reset discards the queued HTTP response before the client reads it, resulting in a connection abort rather than an informative HTTP error response and causing flaky functional tests.
Category: RPC / REST / ZMQ (#5 of 52)
P3 · bug fix
- P3 because it fixes an edge-case bug where Windows clients get TCP resets instead of HTTP error codes
- Improves CI reliability by eliminating intermittent test failures on Windows
P3 because it addresses an edge-case bug where Windows HTTP clients receive an ungraceful TCP reset instead of a 400 or 413 HTTP error code. The issue was observed as an intermittent CI failure in interface_http.py (#35632), making it a worthwhile reliability improvement for Windows users and test stability.
Membership: Modifies core connection lifecycle and response delivery in src/httpserver.cpp and src/httpserver.h.
Factors: security/stability 1, bug 1, performance 0, user value 1, leverage 0
Reviewability: Stale: Needs rebase
- Needs rebase due to merge conflicts with master
- PR is marked draft and author has been inactive for several weeks
The PR has merge conflicts with master (specifically with #35829) and carries the Needs rebase label.
Author status: Silent for 24 days since force-pushing on 2026-08-24; PR is marked draft and needs rebase.
Open concerns:
- pinheadmz and winterrdog questioned the approach of handling lingering close connection flags manually rather than integrating with the HTTPRequest state machine merged in #35735
- Merge conflict with #35829 (hodlinator provided an rebase branch)
Resolved concerns:
- Explanation provided on why force_close and m_disconnect must operate in sequence to allow the response flush before socket teardown
Agreement: Positive
- Concept approval to improve client error handling and test determinism (pinheadmz, hodlinator)
- Questions whether lingering close should integrate into the request state machine (pinheadmz, winterrdog)
- Rebase branch suggestion provided (hodlinator)
Concept ACKed by multiple contributors; design discussions open regarding state machine integration
Reviewers broadly agree that preventing Windows TCP resets on parse errors is desirable, though pinheadmz and winterrdog recommended refining the implementation approach.
- pinheadmz: 'Concept ACK on lingering close to be more polite to clients and improve test determinism. However I am not convinced about the approach.'
- winterrdog: 'concept ACK... request state machine handles the HTTP side, and only minimal connection-level state is needed for the final TCP drain'
- hodlinator: 'Concept ACK... here is a suggestion for how to rebase'
Review verdicts (DrahtBot): 0
- Concept ACK: pinheadmz, frankomosh, winterrdog, hodlinator
Files
8 lines under test/bench/ci.
- src/httpserver.cpp +84/-17
- src/httpserver.h +32/-4
- src/util/sock.cpp +9/-0
- src/util/sock.h +6/-0
- src/test/fuzz/util/net.cpp +2/-0
- src/test/fuzz/util/net.h +2/-0
- src/test/util/net.cpp +2/-0
- src/test/util/net.h +2/-0
Card
This pull request implements a lingering close for the HTTP server when rejecting requests due to malformed headers (400) or excessive payloads (413). On Windows, closing a socket with unread data in the kernel buffer causes a TCP reset that aborts the connection before the client reads the error response, intermittently failing CI tests like interface_http.py. The patch flushes the reply, half-closes the send channel, and drains remaining inbound bytes until EOF or a 1-second timeout. Reviewers have Concept ACKed the change, but the implementation approach remains under discussion and the branch currently requires a rebase against master.