Skip to content

x86: blocking poll() on a connected TCP fd returns ready=0 revents=0x0000 after its full 5 s timeout although the peer wrote and exited (1/24 beast KVM boots) #693

Description

@ryanbreen

Summary

On beast breenix-x86 (KVM, -cpu host, testing,external_test_bins), one
boot in 24 at fix/568-poll-wedge @ efe08b17 has poll_tcp_oracle's stage-3
poll() return ready=0, revents=0x0000 after its full 5000 ms timeout,
even though the peer child ran, wrote and exited cleanly while the parent was
parked:

[POLL_TCP_ORACLE:FAIL:late_lost_wake:ready=0 revents=0x0000 elapsed_ms=5040 delay_ms=80]

This is not the strand that #568's scheduler reorder fixes. The thread is
resumed — by its own timeout, not by the data:

3037: Saved kernel context for blocked thread 23: RIP=0x8000226672 RSP=0xffffc90000c172c8
3470: Process 21 'poll_tcp_oracle_child_21' (thread 35) exited with code 0
3592: Restored kernel context for thread 23: RIP=0x8000226672 RSP=0xffffc90000c172c8
3616: Process 13 'poll_tcp_oracle' (thread 23) exited with code 1

The peer stamps a readiness token, sleeps 80 ms, writes the payload and exits 0
— all of that happens (line 3470) while the parent is blocked from line 3037.
The parent is not woken by the write; it waits out the whole 5 s. And on the
timeout return the re-scan still reports the fd as not readable
(revents=0x0000), which is the part that is hardest to explain away as
scheduling latency.

What is and is not established

Established from the kernel serial: the parent was parked across the peer's
entire lifetime; the peer exited 0; the poll returned 0 ready fds with empty
revents after the full timeout.

Not established: whether the peer's send() was actually issued and its
readiness lost, or whether the peer's own recv of the token never completed
so it never wrote at all. late_peer() exits 0 either way
(let _ = socket::send(...)), so its exit code does not discriminate. A
first step is to make that path report, so the next occurrence separates
"readiness lost" from "peer never wrote".

Rate

1 in 24 merge-candidate boots (~4%) in the round-3 interleaved A/B. The
reorder-reverted arm cannot be compared on this: it emitted a verdict in only
4 of 16 boots because the oracle was stranded in 12 of them.

Serials (in-repo)

  • docs/planning/green-program/sockets/serials/x86-r3-CAND-late-lost-wake-boot9-kernel-20260829.txt
  • docs/planning/green-program/sockets/serials/x86-r3-CAND-late-lost-wake-boot9-user-20260829.txt

Per-boot table: docs/planning/green-program/sockets/AB-2026-08-29-round3.md.

Why this matters for #568

#568 is about a blocking poll() on a connected TCP fd. The hard wedge
(defect 1) and the never-rescheduled strand (defect 2) are both fixed on that
branch and proven. This is a third, rarer shape in the same syscall, and it is
the reason #568 is not being claimed closed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions