Skip to content

fix(process-daemon): wait for readiness instead of polling - #59701

Merged
jlongster merged 1 commit into
devfrom
daemon-poll-loop
Oct 28, 2026
Merged

jlongster merged 1 commit into
devfrom
daemon-poll-loop

Conversation

@jlongster

@jlongster jlongster commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Ported from anomalyco/opencode-pty#31. Part of the stack for anomalyco/opencode-pty#25; builds directly on dev (#59700 has merged), no prerequisite.

Issue for this PR

Fixes anomalyco/opencode-pty#25 (this PR completes it)

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

  • The Unix daemon loop now sleeps in poll(2) on the listener plus a private wake channel (UnixStream::pair, non-blocking, CLOEXEC) instead of a 10 ms sleep. The timeout is the minimum of the next ownership deadline, a 2 s safety net, and the accept back-off.
  • Control notifies under the ownership mutex for every stop/ownership mutation. Control::tick drains the wake channel, observes, detects missed wakes, and ticks under the same lock, so a missed wake is detected deterministically (a warning in release builds; an error in debug builds, with cleanup still running).
  • Handler completion wakes the loop, so finished connections close promptly.
  • EMFILE-class errors (EMFILE, ENFILE, ENOBUFS, ENOMEM) from accept or cancellation now pause accept for 100 ms instead of killing the daemon.
  • Windows keeps its 10 ms poll via stubs. Follow-up: WaitForMultipleObjects on the connect event plus a stop event, and replacing the per-terminal 10 ms root-exit poll.
  • Enables the poll feature of nix in packages/process-daemon/Cargo.toml (no new crates; Cargo.lock is unchanged).

Ported from the opencode-pty commit; the only difference is that connection handlers are spawned with plain thread::spawn, as on dev.

Measurement (macOS, debug builds, idle owned daemon)

Before After
Context switches 96.9/s 1.0/s
Ping round trip 11.6 ms 0.19 ms
Owner close → daemon exit 18.9 ms 3.4 ms

Tests added

  • packages/process-daemon/src/daemon/server.rs:
    • owned_handoff_expiry_keeps_daemon
    • idle_loop_does_not_spin
    • connect_does_not_wait_for_timeout
    • handler_completion_closes_socket_promptly
    • missed_wake_is_detected
    • missed_disconnect_is_detected
    • notify_storm_is_not_missed
  • packages/process-daemon/src/ownership.rs: deadline_tracks_each_state
  • packages/process-daemon/src/transport/unix.rs:
    • wake_is_cloexec_and_nonblocking
    • wake_notify_never_blocks_and_drain_empties
    • listener_wait_reports_each_source
    • listener_wait_rounds_timeout_up

Mutation checks (original PR)

  • Removing the handler-completion (Done) notify fails handler_completion_closes_socket_promptly.
  • Removing the update_ownership notify fails owned_handoff_expiry_keeps_daemon.
  • Making tick notify fails idle_loop_does_not_spin, missed_wake_is_detected, and missed_disconnect_is_detected.

Flakiness runs (original PR)

  • Server module ×10, full lib ×10, concurrent ×5, single-threaded ×3. All passed.

How did you verify your code works?

  • Port validation (macOS arm64, Rust 1.90.0, in packages/process-daemon): cargo fmt --check, cargo clippy --locked --all-targets --all-features -- -D warnings, and cargo test --locked --lib (65 passed) pass.
  • On the stack tip (daemon-windows-poll) the full macOS CI test selection and Windows/Linux cross clippy also pass, and the server module tests (13) passed 3 runs in a row.
  • Original PR: the macOS CI test selection passed: cargo test --locked --lib --test service --test runtime --test ownership --test rows --test ghostty-effects (lib 59, ghostty-effects 5, ownership 8, rows 1, runtime 8 + 1 ignored, service 9), and cross clippy with -D warnings passed for x86_64-pc-windows-msvc and x86_64-unknown-linux-gnu (--all-targets --all-features, ghostty-vt link libs stubbed). Native Windows and Linux rely on CI.

Screenshots / recordings

N/A, internal daemon change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@jlongster
jlongster merged commit 0610a81 into dev Oct 28, 2026
20 checks passed
@jlongster
jlongster deleted the daemon-poll-loop branch October 28, 2026 01:09
alsuren pushed a commit to alsuren/opencode that referenced this pull request Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Idle daemon wakes the CPU ~100 times/s (10 ms sleep in the accept loop)

1 participant