Skip to content

fix(proxy): drain queued data before propagating half-close - #223

Merged
Zxilly merged 1 commit into
masterfrom
fix/proxy-half-close-drain
Sep 30, 2026
Merged

Zxilly merged 1 commit into
masterfrom
fix/proxy-half-close-drain

Conversation

@Zxilly

@Zxilly Zxilly commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Summary

Fix transparent-proxy truncation when a peer half-closes while unread bytes remain.

  • Treat recv/splice returning zero as EOF, and continue draining on HUP/RDHUP
  • Pause an epoll descriptor when it has no useful interests, then re-add it when the opposite side makes forwarding progress
  • Request RDHUP only while reading, so a drained read side cannot busy-spin while output is blocked
  • Preserve partial buffered writes and pending splice bytes before propagating SHUT_WR; close genuine socket errors

Reproduction

Verified on master 4ff67c3770cb2dabc6f1427ef19277df093aafc1 with real loopback TCP sockets:

  1. The target half-closes its response but keeps receiving
  2. The client successfully sends 65,536 bytes, then half-closes its write side
  3. The proxy receives EPOLLIN | EPOLLRDHUP | EPOLLHUP with 65,536 bytes still queued
  4. Master forwards one 16,384-byte buffer, marks EOF from HUP, and closes with 49,152 bytes unread

The fixed handler forwards all 65,536 bytes before propagating EOF. The peers remain open until their receive loops observe EOF.

Regression coverage

The existing proxy test binary now exercises the production C event loop through an internal test bridge, without exposing test hooks in production headers.

  • Both directional half-close regressions on TCP and UNIX sockets
  • Buffered and splice forwarding with small pipes
  • Both payloads and both FINs queued before any dispatch
  • Bidirectional 1 MiB transfers with partial writes and backpressure
  • Complete byte comparisons and checksums, with EOF only after complete delivery
  • HUP pause/re-add and RDHUP no-spin assertions
  • Deferred client registration during target connect and TCP reset cleanup

Validation

  • Native CTest: 170 passed, 6 intentionally inapplicable parameter combinations skipped, 0 failed
  • ASan + UBSan CTest: the same result; leak checking disabled because the local executor does not support LeakSanitizer's process inspection
  • Transport tests repeated 20 times without a failure
  • The new directional regression tests fail against the exact master proxy source
  • GitHub's privileged integration and architecture matrix will run on this PR

This branch is based directly on current master and contains no performance-experiment changes.

@Zxilly
Zxilly marked this pull request as ready for review September 30, 2026 03:34
@Zxilly
Zxilly merged commit 462e5bf into master Sep 30, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant