Repository navigation
The shell reads its input a byte a read, so what is typed after a line is its program's - #1209
Conversation
…e is its program's The shell read standard input through std's `Stdin`, which reads ahead up to 64 bytes. A line typed while the shell was still coming back to its prompt was read together with the one before it, and when that one started a program reading the same input (a nested shell, `cat`), the program never saw its line: it sat in the outer shell's buffer until the program ended. `libc_children`'s `tty_interrupt` stalled on exactly that, about one run in seven, at "the nested line held". The probe typed `cat` early by a defect of its own: - After `echo pipeline-ended` it waited for no line to be held, which was already so before `echo`'s line was handed over, so it waited for nothing. - Its next wait, for the nested shell's line, was then satisfied by `echo`'s stale notice whenever the shell had not yet ended that line. - It typed `cat` at once, both lines were in the pipe when the shell came back to read, and the shell took both. The nested shell blocked in its one read, as the kernel's exit record of it said. The shell now reads the descriptor itself, one byte a read, and no longer sets std's raw mode, which it does not read through. The probe counts a notice at a time and waits for each `echo` line handed and ended. It also types the nested shell's first line with the line that starts it, in one write: the shell reading ahead stalls that step every run instead of one in seven. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… record The closed record asked whether `signal_quit`'s one stall, "driving the child that reloads", was the same defect. It is not: `signal_quit` runs no shell and each of its children has pipes of its own. What its code allows is a signal raised between a child's `armed` and the call it blocks in, which libc runs on its own thread and the call then never sees. That is read from the code and not reproduced, and the new record says so. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A second sighting, in this branch's own control run: `signal_quit`, which runs no shell, ended `stalled driving the child that reloads` on a quiet guest runner. A control that holds that child 200 ms short of its `pause` after it says `armed` stalls the row the same way on main, so the record states the cause and no longer calls it unreproduced. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Review r1 of #1209 at
|
| arm | what it shows |
|---|---|
| main, line end 100 ms late: red at "the nested line held" | The stall is governed by the window between echo's output and its line's end. The old until_lines returned on echo's stale held: true, cat was typed early, and the 64-byte fill took both lines. The shell said /> shell, the nested prompt, and nothing of cat. |
| new probe on main's shell: red, same step | The one-write step detects read-ahead without any timing. |
branch, line end 100 ms late: tty_interrupt green |
The fix holds under the same widening. |
| branch: green | — |
This is consistent with #1189's kernel record of the nested shell sitting in its first read. The closed record's exit asked for "green across repeated runs"; that was written with the cause unknown. A deterministic reproduction and its removal are stronger than a run count against a 1-in-7 rate, so I accept the close. No citation of the closed slug remains at the head or on origin/main.
Revisions. 57b5d38f7 differs from a35358bc4 in the two issue files only, and from ecb718f5f in the new record only. The guest evidence stands for the head. Host CI: 1 of 110 red, the recorded macOS llvm test.
The probe
Clear claim, deterministic failure.
host.poll()yields one notice perMSG_FOREGROUNDframe, so counting one at a time sees every line handed and ended in order.- The added waits after each
echoconsume that line's two notices. This also closes the same stale-notice hazard before the reader step, which the old probe had and nobody had hit. - The combined write adds no boot, and its claim (what follows a line is its program's) is one only real processes sharing a pipe can show.
Other programs with the same defect
None found. I searched system/, apps/, lib/ and sdk/ for code that both spawns and reads standard input: only system/shell. The terminal, console, sshserver and libc's system/popen start a shell on a pipe they write or pass on, and the last two use -c, which reads no input. toybox spawns nothing. The PR's risk note is accurate; nothing to record.
Findings
No BLOCKER.
SUGGESTION
- Evidence
r1/control-signal_quit-child-short-of-its-pause.patch— the published patch is a diff against a newer main, so it also "deletes" issues: what the kernel still holds, by responsibility #1208's three issue files and two of its hunks. It does not apply to9349705ce, the base the record names. The control itself is only the last hunk (three lines insignal_quit.c, which is whata42c5fab8actually is). The record's exit depends on re-running this control, so append the clean hunk and repoint the link. issues/signal-quits-children-can-take-their-signal-before-they-block.md:24— "The cause." is stated as fact. The control shows the window is sufficient to produce the line; neither sighting was observed going through it. It is the only wait the code has on that path, so the inference is strong, but one clause should say it is inferred.issues/signal-quits-children-can-take-their-signal-before-they-block.md:4—severity: [hang]was inherited from the closed record. The new record says the race is the test's own: libc does whatsigreq.rs's header says, andpausehas this window on any POSIX system. The flag puts a racy test into the query for product hangs. Consider dropping it.- Recommended fix for that record, since you asked. It stays in C and loses no coverage:
reloads: blockSIGHUPandSIGTERMbeforearmed(), and wait insigsuspend(&none)both times. libc'spauseissigsuspendwith the thread's own mask (sdk/libc/src/posix_io.rs:1139-1147, the samepoll_untilcall), so nothingpauseshowed is lost, and both windows close. This keeps what only this child claims: a hang-up and a terminate arriving asSIGHUPandSIGTERM.reads: delete it.readcannot unblock a signal atomically. Its two halves are held elsewhere:readansweringEINTRacross a handler withoutSA_RESTARTislibc_read_restarts's claim, ordered by the roster; a quit'sSIGINTrunning its handler and interrupting a blocked call is theppollschild's secondppoll. If a quit interrupting areadspecifically is wanted, it is one more raiser inlibc_read_restarts, not a new program.- The record's enumeration of windows (
reads,reloadstwice) is complete:ignores,blocks,computesandppollshave none.
The rest of the new record checks out: both sightings and their revisions, the control's run and base, the description of libc's routing against sigreq.rs's header, and why libc_read_restarts is a Rust program. Recording it rather than fixing it here is within the rules: its independence is established (no shell, pipes of its own, reproduced on main).
LAND
Problem
libc_children'stty_interruptstalled at "the nested line held" about onerun in seven
(
issues/libc-childrens-tty-interrupt-can-stall-at-the-nested-shells-line.md).Two defects made it, one the product's and one the probe's.
The shell read ahead. It read standard input through std's
Stdin, whosebuffer takes up to 64 bytes a read. A line typed while the shell was still
coming back to its prompt was read together with the one before it. When
that one started a program reading the same input (a nested shell,
cat),the program never saw its line: it sat in the outer shell's buffer.
The probe typed early.
echo pipeline-endedit waited for no line to be held. That wasalready so before
echo's line was handed over, so it waited for nothing.echo'sstale notice whenever the shell had not yet ended that line.
catat once. Both lines were in the pipe when the shell cameback to read, and the shell took both.
record of it said in The loader is the program's, and the kernel parses no ELF #1189's sighting.
Resulting behavior
after a line stays in the pipe for what that line runs. It no longer sets
std's raw mode, which it does not read through.
echoline handedand ended.
it, in one write. A shell that reads ahead stalls that step every run.
Checks
All guest runs are
libc_childrenon the guest runner (QEMU x86-64, 2 CPUs).a35358bc4a31d949e0stalled at the nested line held648b06b319ad2edd7dtty_interruptgreen. The run is red forsignal_quit, see belowsession_lifecycle a_usb_key_reaches_the_console, which type at a terminal's and the console's shellecb718f5fcargo run -- --ci host, macOSa35358bc4issues/the-llvm-environment-test-breaks-git-on-macos.mdThe first control widens the window the stall needs on main's own code: the
probe's stale notice, then both lines read at once. The second is the new
step's negative control. In both the shell had said
/> shelland the nestedprompt, and nothing of
cat.The fourth arm is red, and not for this change.
signal_quit, a Cprogram of the same row that runs no shell, ended
stalled driving the child that reloads. That is its second sighting, recorded in the new issue below.tty_interruptpassed in that run, which is what the arm asks. The host wasquiet (0.02 CPUs of other work).
The head,
57b5d38f7, isa35358bc4plus the two issue files. The shellrows ran at
ecb718f5f, which differs from the head in the new record'stext alone.
Evidence: https://github.com/ToyOSOrg/evidence/tree/9eaaac0994e1468a9135af6ce22188462f8567ac/r1
Records
issues/libc-childrens-tty-interrupt-can-stall-at-the-nested-shells-line.md.Nothing outside
issues/cited it.issues/signal-quits-children-can-take-their-signal-before-they-block.md.The closed record asked whether
signal_quit's stall was the same defect.It is not:
signal_quitruns no shell. Itsreloadschild can take itsSIGHUPbetween sayingarmedand enteringpause. A control that holdsthe child 200 ms short of its
pausestalls the row the same way on main(run 42937578767,
a42c5fab8). It is recorded and not fixed here: a Cprogram cannot order a signal after a block, so the fix is a choice about
what those children claim.
Risks
do; only a shell hands its input on.
🤖 Generated with Claude Code