Skip to content

Set TCP_NODELAY on Python e2e client sockets - #8410

Merged
Amaury Chamayou (achamayou) merged 3 commits into
mainfrom
agents/tcp-nodelay-client-optimization
Sep 22, 2026
Merged

Amaury Chamayou (achamayou) merged 3 commits into
mainfrom
agents/tcp-nodelay-client-optimization

Conversation

@eddyashton

@eddyashton Eddy Ashton (eddyashton) commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Stacked on #8416

This PR targets fix/snapshot-access-races-alternative (#8416), which fixes snapshot selection and subsequent latest-snapshot advancement. The earlier snapshot workaround from this branch has been dropped; the snapshot tests now come entirely from #8416.

#8412 has merged into main. The current #8416 base predates that merge and needs to incorporate it to carry the recovery-test fix into this stack.

Motivation

The pinned httpx 0.23 / httpcore 0.16 HTTP/1.1 client writes request headers and body separately without enabling TCP_NODELAY. Nagle's algorithm can hold the body until the server's delayed ACK arrives, adding roughly 40 ms per POST.

Enable TCP_NODELAY on the Python e2e client's sockets to remove that delay and measure the overall CI runtime impact.

Changes

  • Install a small synchronous httpcore backend in HttpxClient that enables TCP_NODELAY when connecting. The connection setup is shared by HTTP/1.1 and HTTP/2.
  • Enable TCP_NODELAY in RawSocketClient; leave CurlClient unchanged.
  • Pin httpcore to 0.16.3 because the backend integration uses private transport/pool internals.
  • Make the ledger-download test generate signatures by transaction count rather than depending on elapsed request time.
  • Increase the long-lived forwarding test's channel-message limit from 30 to 90 to allow more headroom during key exchange with faster requests. Its request count continues to scale with that limit.

Test infrastructure only; no CHANGELOG entry, recovery padding changes, or LoggingTxs refactor. Kept as a draft to assess CI runtime impact.

@eddyashton
Eddy Ashton (eddyashton) force-pushed the agents/tcp-nodelay-client-optimization branch from 17d0342 to 934c63d Compare September 21, 2026 15:29
@eddyashton Eddy Ashton (eddyashton) added the run-long-test Run Long Test job label Sep 21, 2026
@eddyashton Eddy Ashton (eddyashton) changed the title tests: Set TCP_NODELAY on Python e2e client sockets Set TCP_NODELAY on Python e2e client sockets Sep 21, 2026
@eddyashton
Eddy Ashton (eddyashton) force-pushed the agents/tcp-nodelay-client-optimization branch 3 times, most recently from fae43ba to a3acc6a Compare September 22, 2026 12:12
@eddyashton
Eddy Ashton (eddyashton) changed the base branch from main to agents/open-recovered-service-test September 22, 2026 12:12
@eddyashton
Eddy Ashton (eddyashton) added this pull request to stack #8413 September 22, 2026 12:13
@eddyashton
Eddy Ashton (eddyashton) marked this pull request as ready for review September 22, 2026 12:13
@eddyashton
Eddy Ashton (eddyashton) requested a review from a team as a code owner September 22, 2026 12:13
Copilot AI lite review requested due to automatic review settings September 22, 2026 12:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The HTTPX backend overrides the wrong httpcore method, so TCP_NODELAY is not applied; an explicit compatible dependency is also needed.

Review effort: Lite
Findings: None

What changed in this PR

Test-infrastructure updates reduce TCP latency for Python e2e clients and stabilize timing-sensitive tests.

Changes:

  • Configures TCP_NODELAY for HTTPX and raw-socket clients.
  • Makes ledger chunk generation transaction-driven.
  • Raises the forwarding test’s message limit.
File Summary
tests/​schema.py Forces signatures after each transaction.
tests/​infra/​clients.py Configures client socket behavior; the HTTPX backend method and explicit httpcore dependency require correction.
tests/​e2e_logging.py Raises the node-to-node forwarding message limit.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@eddyashton

Copy link
Copy Markdown
Member Author

Re: the Copilot review's "overrides the wrong httpcore method" concern on tests/infra/clients.py —

I traced the actual call path in httpcore 0.16.3 (the version currently resolved): HTTPConnection._connect() calls self._network_backend.connect_tcp(...) to get the plain TCP stream, then calls stream.start_tls(...) (a method on the returned SyncStream, not on the backend) to upgrade to TLS. connect_tcp is the only place a new connection is established, for both HTTP/1.1 and HTTP/2 (they differ only in the ALPN protocols passed to the same start_tls call). I also verified empirically that ssl.SSLContext.wrap_socket() (which start_tls calls) doesn't create a new OS-level socket — it detaches the fd from the plain socket.socket Python object and reattaches the same fd to the new SSLSocket, so TCP_NODELAY set on the raw socket in connect_tcp survives the TLS wrap intact. ConnectionPool.create_connection() also reads self._network_backend fresh per new connection (not just once at pool construction), so patching pool._network_backend right after httpx.Client(...) construction reliably applies to every connection.

I diffed httpcore.backends.sync.SyncBackend.connect_tcp and ConnectionPool._network_backend across the entire version range httpx 0.23.3 allows (httpcore>=0.15.0,<0.17.0): identical structure throughout. This matches the local regression I measured (recovery_test ~195s → ~162s), so I believe connect_tcp is the correct interception point.

The review's second point ("an explicit compatible dependency is also needed") was valid, though: httpcore wasn't pinned, only transitively constrained by httpx. Since the fix depends on httpcore's private internals rather than any public API, I've pinned httpcore==0.16.3 explicitly (d03a2e9) to prevent a future in-range release from silently changing those internals.

@eddyashton
Eddy Ashton (eddyashton) force-pushed the agents/tcp-nodelay-client-optimization branch from f04f6fd to cad5bba Compare September 22, 2026 13:01
@eddyashton

Copy link
Copy Markdown
Member Author

Update on the test_snapshot_access fix folded into this PR: the first version I pushed was flawed — it derived the expected snapshot from the server's own redirect response, which eliminated the race but also silently weakened the test to only checking internal self-consistency, no longer verifying the server's "latest" endpoint returns the correct snapshot. Caught and corrected (cad5bbaf9f): now tracks which snapshots exist before calling trigger_snapshot() and waits specifically for a new one covering the target seqno, preserving the original assertion that the server's redirect matches precisely the snapshot this test caused.

Base automatically changed from agents/open-recovered-service-test to main September 22, 2026 14:35
@eddyashton
Eddy Ashton (eddyashton) force-pushed the agents/tcp-nodelay-client-optimization branch from ef05d52 to 329e95b Compare September 22, 2026 14:38
@eddyashton
Eddy Ashton (eddyashton) removed this pull request from stack #8413 September 22, 2026 14:40
@eddyashton
Eddy Ashton (eddyashton) changed the base branch from main to fix/snapshot-access-races-alternative September 22, 2026 14:40
@eddyashton
Eddy Ashton (eddyashton) added this pull request to stack #8417 September 22, 2026 14:40
Base automatically changed from fix/snapshot-access-races-alternative to main September 22, 2026 14:58
httpcore 0.16 (pinned by tests/requirements.txt's httpx[http2]==0.23.*,
since later httpx/httpcore versions break other tests) writes request
headers and body as two separate socket writes, and its sync backend
does not set TCP_NODELAY. With Nagle's algorithm enabled, the second
write is held back until the first is ACKed, and CCF's delayed-ACK
timer only fires once it has received the full request, adding ~40ms
of latency to every POST issued by the test client.

Set TCP_NODELAY on the client socket for both HttpxClient (the default
test client) and RawSocketClient. CurlClient is unaffected as curl
already enables TCP_NODELAY by default.

Measured locally: recovery_test dropped from ~195s to ~162s.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TCP_NODELAY (previous commit) removes ~40ms of Nagle/delayed-ACK
latency from every client POST, which exposed two pre-existing test
tunings that implicitly relied on that latency to bound their timing:

- schema_test's "download" sub-test asserted more than 10 ledger
  chunks after 10 app transactions, relying on CCF's default 1000ms
  idle signature interval firing a few extra times during bootstrap
  to pad the count. With faster requests, setup completes quickly
  enough that this padding disappears. Fixed by setting
  sig_tx_interval=1 for this sub-test, so chunk count is driven
  deterministically by transaction count instead of wall-clock time.

- e2e_logging's test_long_lived_forwarding intentionally sets a low
  node_to_node_message_limit to exercise channel key rotation under
  load; its comment already noted that too high a request rate can
  make the hard limit trip mid-flight, invalidating in-flight
  forwarded requests. Faster requests made this race common enough to
  fail CI consistently. Fixed by increasing message_limit from 30 to
  90, giving enough headroom for the higher achievable request rate.

Both were confirmed via repeated local A/B runs (with/without the
TCP_NODELAY change): reliably reproduced with the fix, reliably
passed without it, and reliably pass again with these adjustments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
infra.clients.HttpxClient's _NoDelaySyncBackend relies on httpcore's
private internals (module path, class, method and connection-pool
attribute names) to set TCP_NODELAY. httpx 0.23.3 only constrains
httpcore to >=0.15.0,<0.17.0, so without an explicit pin, a future
httpcore release inside that range could change those internals and
silently break the patch (pip would still resolve cleanly, but
TCP_NODELAY would stop being applied without any test failing).

Pin httpcore==0.16.3 (the version httpx 0.23.3 currently resolves to)
so the dependency matches what the patch was written and tested
against. Verified compatible: `pip install "httpx[http2]==0.23.*"
"httpcore==0.16.3"` resolves without conflict, and connect_tcp /
_network_backend are unchanged across the entire allowed range
(0.15.0 through 0.16.3), so this pin does not change current
behaviour - it only prevents silent drift.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@achamayou
Amaury Chamayou (achamayou) merged commit 386fd48 into main Sep 22, 2026
12 checks passed
@achamayou
Amaury Chamayou (achamayou) deleted the agents/tcp-nodelay-client-optimization branch September 22, 2026 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-long-test Run Long Test job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants