basecamp / basecamp/basecamp-sdk
Event feed: the superseded-poll wait is bounded by two staleness windows, not one, because it borrows the staleness timer
- Dominant language
- Go
- Stars
- 49
- Forks
- 12
- Avg merge
- 20h 47m
- Merged PRs (30d)
- 89
Description
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:
```go
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.
Contributor guide
Research direction
Start by reading catchup.go, especially awaitSupersededPoll, then compare the timing question with #705, #758, and #760. Review §23 and the cross-SDK fixtures that assert timer kinds and exact timer sets. Done means reaching and documenting a decision on a fixed grace phase versus the current staleness-window behavior, including any required specification and SDK changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100