-
Notifications
You must be signed in to change notification settings - Fork 12
docs: update cross-user helper specs for bounded output framing #379
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,10 +88,14 @@ threads instead: | |
| stdin ──────────> │ Main thread │ | ||
| (cancel cmds) │ - reads stdin lines │ | ||
| │ - on cancel: signal child │ | ||
| │ - drains bounded queue, │ ──> stdout | ||
| │ sends {"out":...} lines │ (to session) | ||
| │ │ | ||
| │ Stdout thread │ | ||
| │ - reads child stdout │ ──> stdout | ||
| │ - sends {"out":...} lines │ (to session) | ||
| │ Stdout / Stderr threads │ | ||
| │ - read child pipe in 8 KiB │ | ||
| │ chunks via LineFramer │ | ||
| │ - push lines to bounded │ | ||
| │ queue (256 slots) │ | ||
| │ │ | ||
| │ Child process (job-user) │ | ||
| │ - CREATE_NEW_PROCESS_GROUP │ | ||
|
|
@@ -110,10 +114,24 @@ inside the helper. | |
| - `{"cancel": "TERMINATE"}` → `kill_process_tree(child_pid)` using | ||
| `TerminateProcess` on each process in the tree. | ||
|
|
||
| **I/O multiplexing**: Two threads sharing a channel: | ||
| - Thread 1 (main): reads stdin for cancel commands, signals child on cancel | ||
| - Thread 2: reads child stdout line-by-line, sends `{"out":...}` responses | ||
| - Main thread joins stdout thread after child exits, then sends `{"exited":...}` | ||
| **I/O multiplexing**: Three threads sharing a bounded channel | ||
| (`sync_channel`, 256 slots): | ||
| - Thread 1 (main): polls cancel commands and the channel, sends each queued | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thread count is off by one now that the inventory is being rewritten: Windows actually runs four threads. Suggest: "Four threads — main (drains the output channel, |
||
| line as `{"out":...}`, signals child on cancel | ||
| - Threads 2 and 3: read child stdout and stderr in 8 KiB chunks through the | ||
| shared `LineFramer`, push framed lines to the channel | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "shared That distinction is load-bearing, not cosmetic. A genuinely shared Suggest "the shared |
||
| - After the child exits, main thread drains the channel until both senders | ||
| disconnect, joins the reader threads, then sends `{"exited":...}` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The drain is described as unconditionally terminating, but it can block forever, and the new "Output bounds" rationale is what makes that reachable.
The Unix runner documents exactly this hazard rather than claiming termination ( Two things worth doing:
|
||
|
|
||
| **Output bounds** (same `framer.rs` as the Unix runner): | ||
| - Per line: 64 KiB cap (excess dropped to the next `\n`), invalid UTF-8 | ||
| escaped as `\xNN`, trailing partial line flushed at EOF, JSON payload capped | ||
| at the 128 KiB response limit | ||
| - Aggregate: 256 slots × 64 KiB ≈ 16 MiB. A full channel parks the reader | ||
| threads, the child's pipe fills, and the child stalls | ||
| - Why a bounded channel: the Unix runner reads and emits in one `poll()` loop, | ||
| so a blocked write back-pressures the read for free. Threads break that | ||
| link; the bound restores it | ||
|
|
||
| ### Helper launch (`cross_user_helper.rs`) — add `#[cfg(windows)]` spawn | ||
|
|
||
|
|
@@ -188,6 +206,8 @@ to the child's `hStdInput`, the write end is returned in | |
| Implemented `run_command` for Windows using two threads for I/O multiplexing. | ||
| Handle cancel commands by calling `GenerateConsoleCtrlEvent` or | ||
| `kill_process_tree`. Three integration tests pass (echo, cancel, nonexistent). | ||
| Reader threads route child stdout and stderr through the shared `LineFramer` | ||
| into a bounded channel; see "Output bounds" above. | ||
|
|
||
| ### Step 3: Add `#[cfg(windows)]` spawn in `cross_user_helper.rs` ✅ | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This collapses two exit paths that use opposite orderings, and the "kill process group first" rationale only holds for one of them.
runner.rs:185-190):drain→framer.finish→child.wait()→killpg→ return. Drain happens beforekillpg, andkillpgcomes afterwait(). That ordering is deliberate and safe (POLLHUP means all writers already closed, so EOF is guaranteed), but it is the reverse of what this line says.try_waitpath (runner.rs:202-207):killpg→drain→framer.finish→ return, using the status already obtained fromtry_wait— no secondchild.wait()call.So on the POLLHUP path the stated reason ("closes write ends held by group members" so the drain can reach EOF) does not apply at all, and a reader implementing from this pseudocode would move
killpgahead ofwait()in the POLLHUP branch. Suggest splitting into the two branches, e.g.The "Key details" bullet at line 344-346 has the same ambiguity.