Skip to content

Harden socket response writes - #3355

Merged
lawrencecchen merged 1 commit into
mainfrom
fix-socket-client-backpressure
Apr 30, 2026
Merged

lawrencecchen merged 1 commit into
mainfrom
fix-socket-client-backpressure

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #3340.

Summary:

  • writes complete socket responses instead of relying on one raw write
  • applies a send timeout and SO_NOSIGPIPE to accepted client sockets
  • closes only the failing client connection when response writes fail
  • adds socketpair unit coverage for complete writes and non-reading peers

Verification:

  • git diff --check
  • python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
  • xcodebuild test -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug -destination "platform=macOS" -derivedDataPath /tmp/cmux-fix-socket-client-backpressure -only-testing:cmuxTests/TerminalControllerSocketWriteTests
  • ./scripts/reload.sh --tag fix-socket-client-backpressure
  • dogfood stress against /tmp/cmux-debug-fix-socket-client-backpressure.sock: 590 request-read calls, 192 live checks while 4 no-read firehose sockets held 578 queued large feed.list requests, 80 post-firehose calls, app PID stayed stable
  • tagged CLI smoke: auth status, rpc system.ping, rpc workspace.list with CMUX_SOCKET_PATH=/tmp/cmux-debug-fix-socket-client-backpressure.sock

Note:

  • The runtime firehose stress did not force macOS AF_UNIX write backpressure deterministically because the OS buffered megabytes of unread response data. The deterministic socketpair test covers the blocked-write path; the dogfood stress covers app responsiveness and process stability while no-read sockets are held open.

Summary by cubic

Hardened socket response writes to handle backpressure and avoid hangs. We now send full responses with retries, enforce a 5s send timeout and SO_NOSIGPIPE on client sockets, and only close the failing client on write errors.

  • Bug Fixes
    • Write full payloads via writeAllToSocket (handles partial writes and EINTR).
    • Apply a 5s send timeout and SO_NOSIGPIPE to accepted client sockets to prevent indefinite blocks and SIGPIPE.
    • Replace raw write calls with writeSocketResponse; return early on write failures to keep the listener responsive.
    • Add socketpair unit tests for complete writes and non-reading peers.

Written for commit ddf8f99. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved socket communication reliability with better handling of partial writes and communication interrupts.
    • Added send timeout to prevent indefinite socket hangs.
    • Enhanced error reporting during socket configuration failures.
  • Tests

    • Added new test suite for socket write operations.

@vercel

vercel Bot commented Apr 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 30, 2026 10:33am
cmux-staging Building Building Preview, Comment Apr 30, 2026 10:33am

@greptile-apps

greptile-apps Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens Unix-socket response writes by replacing the single-shot write() call with a retry loop (writeAllToSocket) that handles partial writes and EINTR, applies SO_SNDTIMEO (5 s) and SO_NOSIGPIPE to accepted client sockets via a new configureAcceptedClientSocket helper, and closes only the failing client connection (rather than the listener) when a response write fails. New socketpair-based unit tests exercise both the complete-write path and the blocked-write (non-reading peer) path.

Confidence Score: 4/5

Safe to merge; the one P2 gap (no SO_RCVTIMEO on accepted sockets) is pre-existing and out of scope for this PR.

No P0 or P1 findings. The single P2 observation (missing receive timeout) is pre-existing behavior not worsened by this change. Write-hardening logic, EINTR handling, and the send-timeout integration are all correct.

Sources/TerminalController.swift — configureAcceptedClientSocket missing SO_RCVTIMEO

Important Files Changed

Filename Overview
Sources/TerminalController.swift Core write hardening: adds writeAllToSocket retry loop with EINTR handling, configureSocketSendTimeout/configureNoSigPipe helpers, and configureAcceptedClientSocket combinator; receive timeout missing from accepted sockets is the one P2 gap.
cmuxTests/TerminalControllerSocketWriteTests.swift New unit test file covering the complete-write path and the non-reading-peer (blocked write) path via socketpair; helper duplication of makeSocketTimeout logic is minor.
GhosttyTabs.xcodeproj/project.pbxproj Mechanical Xcode project update registering the new test file in the cmux-unit target; no logic changes.

Sequence Diagram

sequenceDiagram
    participant Listener as Accept Loop
    participant Config as configureAcceptedClientSocket
    participant Handler as handleClient (thread)
    participant Client as Unix Socket Client

    Listener->>Config: fd (accepted socket)
    Config->>Config: configureBlocking(fd)
    Config->>Config: SO_SNDTIMEO = 5 s
    Config->>Config: SO_NOSIGPIPE (macOS)
    Config-->>Listener: nil (ok) or (stage, errno)
    alt config failed
        Listener->>Client: close(fd)
    else config ok
        Listener->>Handler: spawnClientHandler(fd, peerPid)
        Handler->>Client: read() [no RCVTIMEO]
        Client-->>Handler: command line
        Handler->>Handler: processSocketLine()
        Handler->>Handler: writeAllToSocket() loop
        loop partial write / EINTR
            Handler->>Client: write(offset…count)
            Client-->>Handler: n bytes written
        end
        alt write timeout (EAGAIN) or error
            Handler->>Client: close(fd) — only this client
        end
    end
Loading

Reviews (1): Last reviewed commit: "fix: harden socket response writes" | Re-trigger Greptile

Comment on lines +903 to +914
private nonisolated static func configureAcceptedClientSocket(_ fd: Int32) -> (stage: String, errnoCode: Int32)? {
if let errnoCode = configureBlocking(fd) {
return ("accept_client_configure_blocking", errnoCode)
}
if let errnoCode = configureSocketSendTimeout(fd, timeout: socketClientWriteTimeout) {
return ("accept_client_configure_send_timeout", errnoCode)
}
if let errnoCode = configureNoSigPipe(fd) {
return ("accept_client_configure_no_sigpipe", errnoCode)
}
return nil
}

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.

P2 Missing receive timeout on accepted client sockets

configureAcceptedClientSocket now applies SO_SNDTIMEO to guard against slow-reading clients, but it doesn't apply SO_RCVTIMEO. The read() call in handleClient (line 1882) will block indefinitely if a client connects and then goes silent without closing the socket — stalling that handler thread for the connection's lifetime. The code comment at line 1864 acknowledges connect-only probes, but a client that sends a partial command and then stalls would hold the thread forever. Consider adding SO_RCVTIMEO alongside SO_SNDTIMEO to fully bound the accepted-socket lifecycle.

@coderabbitai

coderabbitai Bot commented Apr 30, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82f2c379-46a7-45ad-a437-c507e2fabc38

📥 Commits

Reviewing files that changed from the base of the PR and between 5d1b41d and ddf8f99.

📒 Files selected for processing (3)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Sources/TerminalController.swift
  • cmuxTests/TerminalControllerSocketWriteTests.swift

📝 Walkthrough

Walkthrough

This pull request adds comprehensive socket write handling improvements to TerminalController, including staged client socket configuration with timeout and signal handling, refactored response writing with partial write recovery, and a new test suite exercising the socket write functionality.

Changes

Cohort / File(s) Summary
Xcode Project Configuration
GhosttyTabs.xcodeproj/project.pbxproj
Registers new test file TerminalControllerSocketWriteTests.swift in the cmuxTests target build phases and file references.
Socket Configuration & Response Handling
Sources/TerminalController.swift
Implements staged socket configuration during client accept (blocking mode, 5s send timeout, macOS signal handling), refactors writeSocketResponse to return Bool with new writeAllToSocket routine for handling partial writes and EINTR retries, and improves error reporting for configuration failures.
Socket Write Tests
cmuxTests/TerminalControllerSocketWriteTests.swift
Adds test coverage for writeAllToSocket using AF_UNIX socket pairs, verifying successful small payload writes and timeout behavior under write-blocked conditions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Sockets now sing a clearer tune,
With timeouts set and signals swoon,
Each retry dances 'round EINTR's game,
While tests ensure we're never to blame! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Harden socket response writes' directly reflects the main objective of the PR: improving robustness of socket response handling through better write completion, timeout handling, and error management.
Description check ✅ Passed The PR description comprehensively covers the summary of changes, testing methodology including specific commands and stress test results, and completion of most checklist items, though some checklist items are not explicitly marked.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-socket-client-backpressure

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@lawrencecchen
lawrencecchen merged commit 9e80a39 into main Apr 30, 2026
22 checks passed
@lawrencecchen
lawrencecchen deleted the fix-socket-client-backpressure branch April 30, 2026 10:49

This branch was successfully deployed

1 active deployment
Preview – cmux — ddf8f99b Deployed Apr 30, 2026 by vercel[bot]
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