Skip to content

Widen the EINTR gate to waits it could not see - #321

Merged
jserv merged 1 commit into
mainfrom
eintr
Aug 22, 2026
Merged

Widen the EINTR gate to waits it could not see#321
jserv merged 1 commit into
mainfrom
eintr

Conversation

@jserv

@jservjserv commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

The gate classifies a function when it names EINTR, calls syscall_restart_forbid, or calls a name in its wait-helper list. That list held one name. Everything reaching a wait through io_xfer, io_retry_backoff or net_wait_or_interrupted therefore decided the restart question outside the gate's view: the receive half of the socket layer, the read and write path, flock, semop and splice among them.

Deriving the list from a naming convention was tried first and does not hold. The tree disagrees with any suffix rule in both directions. connect_or_interrupted carries the suffix, while
net_recv_zero_payload_gate and io_xfer do not and report EINTR upward all the same. Derivation also means deleting an inventory entry shrinks the regex, which is the gate going quiet at the moment it should complain. The list is spelled out instead, under two assertions: it may not be empty, since the empty alternation matches every call in the tree and buries the answer under a thousand unclassified functions, and every name in it has to carry a classification of its own.

sys_recvmmsg forbade the restart only when its own poll returned EINTR, which is the single case where the timeout had not been spent. The case that matters is the other one: a poll that reports readable after most of the interval, a sibling that takes the datagram, and an interrupted wait inside the first sys_recvmsg reaching the guest as EINTR with nothing received. The restart re-runs the call with the full timeout. The forbid is unconditional now, so every exit past the poll inherits it.

proc_wait_autoreap_children forbids as well, because its loop can reap an exited child before the backoff reports EINTR. Whether that is a consumption the restart cannot recover is arguable in the other direction: the rescan skips entries it has already marked inactive and reaches the same ECHILD, which is what POSIX gives a waiter under SA_NOCLDWAIT once the children are gone. Recorded here as the conservative reading.


Summary by cubic

Widened the EINTR gate to cover waits reached via common helpers and tightened restart semantics where timeouts or state can be consumed. Previously the gate only saw direct EINTR mentions or a single wait helper; now it enumerates helpers explicitly and asserts coverage. Behavior changes: sys_recvmmsg used to forbid restart only when its poll returned EINTR; it now forbids restart for every exit after the poll to avoid resetting a relative timeout. proc_wait_autoreap_children now forbids restart on interrupted waits to avoid observing different process state.

Reviewer notes

  • scripts/check-eintr-contract.py: enumerates EINTR helpers (e.g., io_xfer, io_retry_backoff, net_wait_or_interrupted, connect_nonblock_wait, connect_or_interrupted, net_recv_zero_payload_gate) and asserts the list is non-empty and every helper is classified; HELPER_RE is built from this list.
  • Adds restartability classifications for key paths: recvmsg/recvmmsg, accept/connect, read/write/readv/writev, flock, semop, splice/vmsplice, and proc waiters.
  • sys_recvmmsg: unconditional syscall_restart_forbid() after poll ensures a restart cannot restart the relative timeout; the vlen==1 fast path (no timeout) is unchanged.
  • proc_wait_autoreap_children: calls syscall_restart_forbid() when io_retry_backoff returns an error, preventing re-execution after a child may have been reaped.

Written for commit 74c4402. Summary will update on new commits.

Review in cubic

The gate classifies a function when it names EINTR, calls
syscall_restart_forbid, or calls a name in its wait-helper list. That
list held one name. Everything reaching a wait through io_xfer,
io_retry_backoff or net_wait_or_interrupted therefore decided the
restart question outside the gate's view: the receive half of the socket
layer, the read and write path, flock, semop and splice among them.
Deriving the list from a naming convention was tried first and does not
hold. The tree disagrees with any suffix rule in both directions.
connect_or_interrupted carries the suffix, while
net_recv_zero_payload_gate and io_xfer do not and report EINTR upward
all the same. Derivation also means deleting an inventory entry shrinks
the regex, which is the gate going quiet at the moment it should
complain. The list is spelled out instead, under two assertions: it may
not be empty, since the empty alternation matches every call in the tree
and buries the answer under a thousand unclassified functions, and every
name in it has to carry a classification of its own.
sys_recvmmsg forbade the restart only when its own poll returned EINTR,
which is the single case where the timeout had not been spent. The case
that matters is the other one: a poll that reports readable after most
of the interval, a sibling that takes the datagram, and an interrupted
wait inside the first sys_recvmsg reaching the guest as EINTR with
nothing received. The restart re-runs the call with the full timeout.
The forbid is unconditional now, so every exit past the poll inherits
it.
proc_wait_autoreap_children forbids as well, because its loop can reap
an exited child before the backoff reports EINTR. Whether that is a
consumption the restart cannot recover is arguable in the other
direction: the rescan skips entries it has already marked inactive and
reaches the same ECHILD, which is what POSIX gives a waiter under
SA_NOCLDWAIT once the children are gone. Recorded here as the
conservative reading.

@cubic-dev-aicubic-dev-aiBot 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.

No issues found across 3 files

Re-trigger cubic

@jserv
jserv merged commit 628181b into mainAug 22, 2026
14 checks passed
@jserv
jserv deleted the eintr branch August 22, 2026 13:45
Sign up for freeto 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

@jserv