Skip to content

Gate the Ctrl-C handler's output on input typed after the echo - #1352

Merged
ejc3 merged 1 commit into
mainfrom
exec-parity-ctrl-c-echo-order
Oct 27, 2026
Merged

ejc3 merged 1 commit into
mainfrom
exec-parity-ctrl-c-echo-order

Conversation

@ejc3

@ejc3 ejc3 commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

it_ctrl_c_interrupts in test_exec_matches_podman_exec compared an output order that the kernel does not fix. This changes the case so the order is fixed, and adds a check over the case table that rejects the old shape.

The failure

Host-Root-x64-SnapshotEnabled on main failed TRY 1 and passed on retry:

it_ctrl_c_interrupts [fcvm exec] stdout: podman [17 bytes] "READY\r\n^CCAUGHT\r\n" fcvm [17 bytes] "READY\r\nCAUGHT\r\n^C"

The same bytes in a different order. The order comes from the guest kernel's line discipline, not from fcvm: the client's terminal is raw before the session starts (RawModeGuard::enter in src/commands/tty.rs), so the only ^C echo is the guest PTY's, and fc-agent forwards the master's bytes in the order it reads them.

Mechanism

For a signal key, n_tty_receive_signal_char (drivers/tty/n_tty.c, 6.18.50) calls isig() first, which sends SIGINT, and only then echo_char() + commit_echoes(). Below the commit watermark commit_echoes writes nothing; the echo goes out in flush_echoes() at the end of __receive_buf. The shell's handler runs on another CPU, and its echo CAUGHT (n_tty_write -> process_echoes) sends only echoes already marked. If it writes between __isig and echo_char, CAUGHT\r\n reaches the master before ^C. On bare metal the window is a few instructions; in a VM the vCPU running the flush work can be descheduled by the host inside it. 600 runs of the same script on a host PTY (300 unpinned, 300 pinned to one CPU) all came out ^C first.

Fix

The handler now reads a line before it prints, and the case types that line only after it has seen ^C:

trap 'read line; echo CAUGHT; exit 130' INT; ...
steps: WaitFor("READY"), Type("\x03"), WaitFor("^C"), Type("\n")

A newline's echo is marked (commit_echoes) before the line wakes the reader (n_tty_receive_handle_newline), and n_tty_write sends marked echoes before the handler's bytes, so the expected output is READY\r\n^C\r\nCAUGHT\r\n for podman and fcvm alike. The case still checks that Ctrl-C reaches the command, that the PTY echoes it, and exit 130.

a_typed_signal_key_is_compared_only_after_its_echo checks every case that compares stdout: a Type step holding VINTR, VQUIT or VSUSP must be followed by WaitFor of its echo and then a Type. It also asserts that the check fires on the old step list.

Contract: test harness only, no product code. Downstream impact: removes one source of retries in the Host-Root lanes.

Evidence

Before the case change, the new test failed:

it_ctrl_c_interrupts: step 1 types 0x03; the next steps must be WaitFor("^C") and a Type the handler waits for

With it:

$ make test-root FILTER="-E 'binary(test_exec_podman_parity)'"
PASS a_typed_signal_key_is_compared_only_after_its_echo
it_ctrl_c_interrupts  podman exec      exit Some(130)
it_ctrl_c_interrupts  fcvm exec        exit Some(130)
it_ctrl_c_interrupts  fcvm exec --vm   exit Some(130)
66 cases, 0 differences
PASS test_exec_matches_podman_exec

The gated script on a host PTY gave READY\r\n^C\r\nCAUGHT\r\n in 100 of 100 runs. make lint passes.

it_ctrl_c_interrupts compared "READY\r\n^CCAUGHT\r\n" byte for byte, but
n_tty sends SIGINT (isig) before it queues the "^C" echo and writes the
echo only at the end of the receive call (flush_echoes). A handler that
prints at once can write in between. On main, Host-Root-x64-SnapshotEnabled
failed TRY 1 with fcvm returning "READY\r\nCAUGHT\r\n^C": same bytes, the
guest kernel's order.

- The handler now runs `read line` before `echo CAUGHT`, and the case
  types "\n" only after it has seen "^C". A newline's echo is marked
  before the line wakes the reader and n_tty_write sends marked echoes
  first, so both sides produce "READY\r\n^C\r\nCAUGHT\r\n".
- a_typed_signal_key_is_compared_only_after_its_echo checks every case
  that compares stdout: a Type step holding VINTR, VQUIT or VSUSP must be
  followed by WaitFor of its echo and then a Type. It fails on the old
  table and asserts that it fires on the old step list.

Tested: the new test failed on the unchanged table and passes with the
fix; make test-root FILTER="-E 'binary(test_exec_podman_parity)'": 2
passed, 66 cases, 0 differences, it_ctrl_c_interrupts exit 130 for all
three targets.
@coderabbitai

coderabbitai Bot commented Oct 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 56 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 12f0ad65-27b0-41fb-9379-1517c4facd28
Commits

Reviewing files that changed from the base of the PR and between ece9bb1 and 941b553.

Files selected for processing: 1
  • tests/test_exec_podman_parity.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-27T01:32:14.016775Z 941b553 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ejc3

ejc3 commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 941b553e21

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ejc3
ejc3 merged commit 89b3148 into main Oct 27, 2026
7 of 15 checks passed
@ejc3
ejc3 deleted the exec-parity-ctrl-c-echo-order branch October 27, 2026 01:32
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