Skip to content

Fix: isolate Remote L3 daemon per-connection send failures - #1427

Merged
ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:fix/remote-l3-daemon-send-isolation
Jul 21, 2026
Merged

Fix: isolate Remote L3 daemon per-connection send failures#1427
ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:fix/remote-l3-daemon-send-isolation

Conversation

@ChaoWao

@ChaoWao ChaoWao commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

The shared Remote L3 control daemon (remote_l3_worker.serve) treated a
send failure on one parent connection as fatal to the whole
daemon. When a timed-out parent closed its socket before the daemon
replied:

  1. the success-reply _send_json raised BrokenPipeError,
  2. the except handler issued a second, bare _send_json on the same
    dead connection, which also raised,
  3. that second exception escaped the with conn block and the
    while True accept loop into finally: server.close().

Blast radius (corrected)

The daemon is the control plane for starting sessions. Already-ready
sessions talk to their parent over their own direct command/health
sockets, so the daemon's death does not tear those down. What it does
kill is the current, still-undelivered runner and every subsequent
session-start request to that host
— a shared-fate failure of the host's
control plane, plus an orphaned runner that lingered until its own
session_timeout_s.

Changes (python/simpler/remote_l3_worker.py)

  • _start_session now returns (reply, proc). The live, ready runner
    handle is handed back to the caller instead of being reaped internally,
    so the caller can reclaim it if the reply is never delivered. A failed
    handshake is still killed + reaped in place and returns proc=None.
    (A successful sendall only means the bytes were queued locally, not
    that the parent read them; unobserved receipt would need an ACK / lease,
    which sim does not have.)
  • New _serve_connection owns one parent connection with full
    isolation and a closed ownership transaction:
    • any Exception on the socket affects only that session, never the
      accept loop;
    • KeyboardInterrupt / SystemExit are BaseException, not
      Exception, so they propagate for a clean daemon shutdown instead
      of becoming a spurious error reply;
    • a live runner is disposed of exactly once on every exit path —
      handed to the reaper only once its send returns, otherwise reclaimed
      by a finally. This covers the send failing, the reaper thread
      failing to launch, and a control exception unwinding mid-connection.
  • serve delegates to _serve_loop which stops when the listener
    closes, so the loop is deterministically testable and tearable-down.

Testing

Repro-first (.claude/rules/discipline.md §3): every new/updated test was
confirmed to fail against the pre-fix source and against interim
versions lacking the finally / Exception-narrowing.

  • Deterministic _serve_connection unit tests: hand-off after send
    returns, reclaim on send failure, reclaim on reaper-launch failure,
    error-reply isolation, truncated-frame / EOF, real-error reporting,
    control-exception propagation (with exactly-once reclaim).
  • _serve_loop unit test: multi-connection survival + clean stop on
    listener close.
  • Two bounded real-socket tests (dead parent, truncated frame): the
    next connection is still served; the server thread is joined with no
    listener leak and no port-rebind race.

Device-free; runs under the py UT suite.

  • Unit / simulation tests pass (test_remote_l3_lifecycle.py +
    test_remote_startup_budget.py; ruff + pyright clean)
  • Hardware tests pass (N/A — device-free control-plane change)

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5d7d424a-e602-4491-aa22-02b259b55d30

📥 Commits

Reviewing files that changed from the base of the PR and between 1319744 and 6bfa992.

📒 Files selected for processing (2)
  • python/simpler/remote_l3_worker.py
  • tests/ut/py/test_remote_l3_lifecycle.py

📝 Walkthrough

Walkthrough

Changes

Runner lifecycle and connection isolation

Layer / File(s) Summary
Session startup ownership contract
python/simpler/remote_l3_worker.py, tests/ut/py/test_remote_l3_lifecycle.py
_start_session returns (reply, proc), leaving successful runners live for the caller and reclaiming failed runners exactly once.
Per-connection serving and cleanup
python/simpler/remote_l3_worker.py, tests/ut/py/test_remote_l3_lifecycle.py
Handshake, reply, reaper handoff, error handling, accept-loop shutdown, and truncated-frame isolation are implemented and tested.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Parent
  participant _serve_loop
  participant _serve_connection
  participant _start_session
  participant RunnerReaper
  Parent->>_serve_loop: connect and send handshake
  _serve_loop->>_serve_connection: accept connection
  _serve_connection->>_start_session: validate and start runner
  _start_session-->>_serve_connection: reply and runner handle
  _serve_connection-->>Parent: send success reply
  _serve_connection->>RunnerReaper: hand off runner
Loading

Possibly related PRs

Poem

A rabbit hops where runners start,
With careful paws, they’re kept apart.
Replies go out, reapers wait,
Dead links no longer seal the gate.
Truncated frames just fade from sight—
The server bounds onward through the night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.89% 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 clearly summarizes the main change: isolating per-connection send failures in the Remote L3 daemon.
Description check ✅ Passed The description accurately matches the implemented changes and test coverage for connection isolation and runner cleanup.
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.

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

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request refactors connection handling in the remote L3 worker by introducing a dedicated _serve_connection function. This isolates individual sessions, ensuring that a broken pipe or send failure on a reply does not crash the main accept loop, and properly reclaims orphaned runner processes. Corresponding unit tests have been added to verify these behaviors. The review feedback points out a critical issue where catching BaseException in _serve_connection could swallow system-terminating exceptions like KeyboardInterrupt, and suggests catching Exception instead.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread python/simpler/remote_l3_worker.py Outdated
@ChaoWao
ChaoWao force-pushed the fix/remote-l3-daemon-send-isolation branch 3 times, most recently from 857dfc8 to 6bfa992 Compare July 21, 2026 13:57
The shared Remote L3 control daemon's accept loop treated a send failure
on one parent connection as fatal to the whole daemon. When a timed-out
parent closed its socket before the daemon replied, the success-reply
send raised BrokenPipe, the except handler issued a second bare send on
the same dead connection which also raised, and that exception escaped
the accept loop into `finally: server.close()`.

The daemon is the control plane for *starting* sessions; already-ready
sessions talk to their parent over their own direct command/health
sockets, so its death does not tear those down. What it does kill is the
current, still-undelivered runner and every subsequent session-start
request to that host — a shared-fate failure of the host's control plane.

- `_start_session` now returns `(reply, proc)`: the live ready runner is
  handed back to the caller instead of being reaped internally, so the
  caller can reclaim it if the reply is never delivered. A failed
  handshake is still killed + reaped in place and returns `proc=None`. A
  successful send only means the bytes were queued locally, not that the
  parent read them (unobserved receipt would need an ACK / lease).
- New `_serve_connection` owns one connection with full isolation and a
  closed ownership transaction: any `Exception` affects only that session;
  `KeyboardInterrupt`/`SystemExit` (BaseException) propagate for clean
  shutdown instead of becoming a spurious error reply; and a live runner
  is disposed of exactly once on every exit path — handed to the reaper
  only once its send returns, otherwise reclaimed by a `finally`. This
  covers the send failing, the reaper thread failing to launch, and a
  control exception unwinding mid-connection.
- `serve` delegates to a `_serve_loop` that stops when the listener
  closes, so the loop can be driven and torn down deterministically.

Tests reproduce each gap first (all fail against the pre-fix source and
against interim versions lacking the finally/Exception narrowing):
deterministic `_serve_connection` unit tests for hand-off, reclaim on
send failure, reclaim on reaper-launch failure, error-reply isolation,
truncated-frame/EOF, real-error reporting, and control-exception
propagation (with exactly-once reclaim); a `_serve_loop` test for
multi-connection survival and clean stop; and two bounded real-socket
tests (dead parent, truncated frame) that serve the next connection and
join their server thread with no listener leak or port race.
@ChaoWao
ChaoWao merged commit 8199971 into hw-native-sys:main Jul 21, 2026
30 of 31 checks passed
@ChaoWao
ChaoWao deleted the fix/remote-l3-daemon-send-isolation branch July 21, 2026 14:18
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