Skip to content

Event feed: the superseded-poll wait is bounded by two staleness windows, not one, because it borrows the staleness timer #785

Description

@jeremy

Raised by Codex on #705 (catchup.go, awaitSupersededPoll).

The mechanism is real and is exactly what the function already documents. What is not
true is the thread's premise that a promised bound is being missed: the code publishes
two staleness windows, deliberately, and explains why. This issue carries the design
question underneath it, which #705 flagged as wanting a §23 conversation rather than a
patch.

The mechanism

awaitSupersededPoll has a fixed deadline read from the injected clock at the deferral
instant, but no timer of its own. It wakes on the staleness timer and on re-arms, and
checks the deadline after every wake:

select {
case r := <-done: ...
case <-l.runCtx.Done(): ...
case <-at.lc.stale.rearmed():
case <-staleTimer.C():
        if _, ok := at.lc.stale.evaluate(staleGen); ok { ... }
}
if !l.cfg.clock.Now().Before(deadline) {
        return pollAttempt{superseded: true}
}

So the wait wakes on the window's cadence, not the deadline's. A frame arriving shortly
before the deadline re-arms staleness; the next wake is a full window later; the lapse is
observed then. Worst case is just under two windows — and it cannot exceed two, because by
the first re-armed firing the clock is necessarily past a deadline set one window after
the deferral, so a second re-arm is never waited out.

Codex's example is right on the mechanism and wrong on the consequence it names:
"advancing virtual time to the promised bound leaves the stalled poll active" assumes a
promised bound of one window. Nothing promises one. §23 puts the obligation on the seam
("Prompt return required"), and the function's own comment publishes two.

The question

Should the superseded-poll wait get a fixed grace phase — immune to frame resets and to
the staleness suspension rule — instead of borrowing the staleness window?

For: the bound would be the one stated; virtual-time tests could advance to it exactly;
and the "two windows" caveat would leave a public timing contract.

Against, and the reason #705 did not do it: §23 pins exactly six kebab-case timer kinds
and every state's exact timer set, both asserted by the cross-SDK fixtures.
A dedicated
timer is a seventh kind — a spec change across six SDKs for what is otherwise a Go-local
wait. Shortening the re-armed window instead is not available either: a staleness window
that is not a staleness window is a different bug.

Related

#758 is a different cause in the same wait — pollPage's select has no staleness case at
all, so a silently half-open socket is unobservable before any frame is deferred. #760
notes that the same single-slot deferral is the obstacle in both. These three want deciding
together, and a grace phase is one of the shapes #758 costs.

Also noted in #705 and worth folding into the same decision: SPEC's published raw-frame
ceiling still says pump depth, while drain's budget is pumpDepth+1 and that +1 is
load-bearing. That is a spec correction across six SDKs rather than a Go change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions