fix(latest,web): finish the atomic deletion and honest empty states (#145)
Review fixes on top of cb2b104:
- Delete the write-only in-memory refuseUntil map and setRefusalBackoff now
that status.go is gone: the pass gate reads the durable stamp, so the map
was half-deleted dead state (spec review C-1).
- An outcome-query failure no longer blanks the Lane table into the false
'no data yet': rows render with 'none observed' chips instead (I-1).
- due-query and eligible-count skips get their own sentences instead of the
merged 'check failed' (I-2).
- ADR-0012 constrain wording corrected and trailing newline added (M-1)
This commit is contained in:
@@ -57,11 +57,12 @@ type Poller struct {
|
|||||||
|
|
||||||
// refuseUntil gates a Site's Lane after it refused twice in one run: no
|
// refuseUntil gates a Site's Lane after it refused twice in one run: no
|
||||||
// Series of that Site is attempted again before this time (issue #100).
|
// Series of that Site is attempted again before this time (issue #100).
|
||||||
|
// The stamp is durable — the pass gate reads it from the store, so a
|
||||||
|
// restart does not forget the refusal; nothing of it lives in memory.
|
||||||
// browserDownAt is when a browser Lane last lost the sidecar; the other
|
// browserDownAt is when a browser Lane last lost the sidecar; the other
|
||||||
// browser Lanes skip their passes for the next RefuseBackoff, so a
|
// browser Lanes skip their passes for the next RefuseBackoff, so a
|
||||||
// restarting Chrome does not stamp one Series per pass per Lane (story 20).
|
// restarting Chrome does not stamp one Series per pass per Lane (story 20).
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
refuseUntil map[string]time.Time
|
|
||||||
browserDownAt time.Time
|
browserDownAt time.Time
|
||||||
// coverWG tracks in-flight cover work. Covers heal in the background so a
|
// coverWG tracks in-flight cover work. Covers heal in the background so a
|
||||||
// slow cover host cannot delay the next Series-page Poll; tests join it
|
// slow cover host cannot delay the next Series-page Poll; tests join it
|
||||||
@@ -437,12 +438,8 @@ func (p *Poller) runLanePass(ctx context.Context, name string, paced bool) time.
|
|||||||
log.Printf("latest poll %s: due=%d checked=%d", name, len(due), fig.Checked)
|
log.Printf("latest poll %s: due=%d checked=%d", name, len(due), fig.Checked)
|
||||||
}
|
}
|
||||||
if refusals >= 2 {
|
if refusals >= 2 {
|
||||||
p.setRefusalBackoff(name, now.Add(RefuseBackoff))
|
|
||||||
// The refusal outlives the process: the durable stamp gates a restart,
|
// The refusal outlives the process: the durable stamp gates a restart,
|
||||||
// so a Site that just told us to back off is not re-probed. The
|
// so a Site that just told us to back off is not re-probed.
|
||||||
// in-memory twin is still written because the Lane status block reads
|
|
||||||
// it directly; the gate reads the durable stamp, so a restart does not
|
|
||||||
// forget the refusal.
|
|
||||||
if err := p.Store.SetLaneRefusal(name, now.Add(RefuseBackoff).UnixMilli()); err != nil {
|
if err := p.Store.SetLaneRefusal(name, now.Add(RefuseBackoff).UnixMilli()); err != nil {
|
||||||
log.Printf("latest poll %s: persist refusal: %v", name, err)
|
log.Printf("latest poll %s: persist refusal: %v", name, err)
|
||||||
}
|
}
|
||||||
@@ -511,15 +508,6 @@ func (p *Poller) countEligible(site string) (int, error) {
|
|||||||
return p.Store.EligibleSeriesCount(site)
|
return p.Store.EligibleSeriesCount(site)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Poller) setRefusalBackoff(name string, until time.Time) {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
if p.refuseUntil == nil {
|
|
||||||
p.refuseUntil = make(map[string]time.Time)
|
|
||||||
}
|
|
||||||
p.refuseUntil[name] = until
|
|
||||||
}
|
|
||||||
|
|
||||||
// setBrowserDown records when a browser Lane lost the sidecar. It is Poller
|
// setBrowserDown records when a browser Lane lost the sidecar. It is Poller
|
||||||
// state rather than pass state so the other browser Lanes see it too.
|
// state rather than pass state so the other browser Lanes see it too.
|
||||||
func (p *Poller) setBrowserDown(now time.Time) {
|
func (p *Poller) setBrowserDown(now time.Time) {
|
||||||
|
|||||||
@@ -84,13 +84,14 @@ func (h *Handler) lanesView() lanesView {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
outcomes, err := h.store.LanePassOutcomes(time.Now().Add(-ownerWindow).UnixMilli())
|
outcomes, err := h.store.LanePassOutcomes(now.Add(-ownerWindow).UnixMilli())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// The rows are complete without the chips, so a failed outcome sum
|
||||||
|
// must not blank the table into "no data yet" — that is the confident
|
||||||
|
// wrong statement the page exists to avoid. Every row renders "none
|
||||||
|
// observed" instead, which is honest.
|
||||||
log.Printf("admin lanes: outcomes: %v", err)
|
log.Printf("admin lanes: outcomes: %v", err)
|
||||||
return lanesView{
|
outcomes = nil
|
||||||
PollerOff: v.PollerOff,
|
|
||||||
BrowserConfigured: v.BrowserConfigured,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bySite := make(map[string]store.SiteOutcomes, len(outcomes))
|
bySite := make(map[string]store.SiteOutcomes, len(outcomes))
|
||||||
for _, o := range outcomes {
|
for _, o := range outcomes {
|
||||||
@@ -193,8 +194,11 @@ func laneState(p store.LanePass, now time.Time) (phrase string, good, attention
|
|||||||
case latest.SkipAsleep:
|
case latest.SkipAsleep:
|
||||||
phrase = "browser asleep"
|
phrase = "browser asleep"
|
||||||
good = true
|
good = true
|
||||||
case latest.SkipDueQuery, latest.SkipEligibleCount:
|
case latest.SkipDueQuery:
|
||||||
phrase = "check failed"
|
phrase = "due query failed"
|
||||||
|
attention = true
|
||||||
|
case latest.SkipEligibleCount:
|
||||||
|
phrase = "eligible count failed"
|
||||||
attention = true
|
attention = true
|
||||||
case latest.SkipNothingEligible:
|
case latest.SkipNothingEligible:
|
||||||
phrase = "nothing eligible"
|
phrase = "nothing eligible"
|
||||||
|
|||||||
@@ -39,6 +39,6 @@ confident zero.
|
|||||||
The poller still owns the writes: each pass exit records one row (ADR-0010),
|
The poller still owns the writes: each pass exit records one row (ADR-0010),
|
||||||
and a pass that returns before gathering figures carries the previous pass's
|
and a pass that returns before gathering figures carries the previous pass's
|
||||||
numbers forward instead of recording zeroes. A skip is a stable wire string;
|
numbers forward instead of recording zeroes. A skip is a stable wire string;
|
||||||
`asleep` never counts toward `Attention`. When polling is switched off the
|
`asleep` never counts toward `Attention`. When polling is switched off
|
||||||
page must say so, and the browser statusline appears only when a poller
|
(`LATEST_CHAPTER_POLL_ENABLED` unset) the page must say so, and the browser
|
||||||
actually answers.
|
statusline appears only when polling is switched on.
|
||||||
Reference in New Issue
Block a user