#35780 http: linger-close after parse errors so clients can read the reply

full analysis

https://github.com/bitcoin/bitcoin/pull/35780 · b-l-u-e · +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

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.

Data

dossier JSON · extract JSON · model openrouter/google/gemini-3.8-flash, generated 2026-09-17T16:20, confidence high, input hash 5f354a5834b573d6