feat(web): read admin lanes from the durable pass log, not a poller snapshot (#145)
The Lanes page now projects store.LatestLanePasses and the owner-window outcomes (store.LanePassOutcomes) into per-Site rows instead of reading an in-memory Poller snapshot, so a deploy answers the instant the store is up. Browser configuration is a config fact and reachability is derived from recent browser-Site passes inside latest.RefuseBackoff. This atomically deletes the in-memory path in the same commit that makes the page read the DB: latest/status.go (LaneState, Status, LaneStatus, recordLaneState) and the web.LaneReporter seam plus fakeLanes are gone, and latest.refuseBackoff is renamed latest.RefuseBackoff at every callsite. ownerWindow (#142) is referenced, never declared (contract C2)
This commit is contained in:
@@ -1308,7 +1308,7 @@ func TestRunOnceOrdersBySharednessThenAge(t *testing.T) {
|
||||
}
|
||||
|
||||
// Two refusals in one pass stop the Lane: the remaining Series stay unstamped
|
||||
// and due, and the Lane backs off for refuseBackoff before trying the Site
|
||||
// and due, and the Lane backs off for RefuseBackoff before trying the Site
|
||||
// again. One hostile Site burns only its own Lane's budget (issue #100).
|
||||
func TestRunOnceSiteRefusalSkipsRestOfLaneAndBacksOff(t *testing.T) {
|
||||
s, _ := newTestStore(t)
|
||||
@@ -1391,11 +1391,9 @@ func TestBrowserLaneWakeThresholds(t *testing.T) {
|
||||
if got := browser.callCount(); got != 0 {
|
||||
t.Fatalf("browser fetches with 3 freshly-due series = %d, want 0 (Chrome stays asleep)", got)
|
||||
}
|
||||
// The owner's page reads this state off the snapshot, and Due-without-
|
||||
// Checked has to be distinguishable there from a Lane that has stopped.
|
||||
if lane := laneByName(t, p, "kagane"); !lane.Asleep || lane.Due != 3 || lane.Checked != 0 {
|
||||
t.Fatalf("asleep kagane lane = %+v, want Asleep with 3 due and 0 checked", lane)
|
||||
}
|
||||
// The skipped pass still records its row, carrying the due count it never
|
||||
// read; the Lanes page (issue #145) reads that row — see
|
||||
// TestRunLanePassRecordsEveryExit/"asleep".
|
||||
// 5 due crosses the count threshold.
|
||||
for i := 3; i < 5; i++ {
|
||||
seed(i)
|
||||
@@ -1404,9 +1402,6 @@ func TestBrowserLaneWakeThresholds(t *testing.T) {
|
||||
if got := browser.callCount(); got != 5 {
|
||||
t.Fatalf("browser fetches with 5 due series = %d, want 5", got)
|
||||
}
|
||||
if lane := laneByName(t, p, "kagane"); lane.Asleep {
|
||||
t.Fatalf("woken kagane lane still reports Asleep: %+v", lane)
|
||||
}
|
||||
// A single long-neglected series wakes the browser by age alone.
|
||||
seedForCheck(t, s, "kagane:ancient", "https://kagane.to/series/ancient", 0)
|
||||
p.runOnce(context.Background())
|
||||
@@ -1415,19 +1410,6 @@ func TestBrowserLaneWakeThresholds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// laneByName pulls one Lane out of the poller's snapshot, failing rather than
|
||||
// returning a zero LaneState a caller would assert against by accident.
|
||||
func laneByName(t *testing.T, p *Poller, site string) LaneState {
|
||||
t.Helper()
|
||||
for _, lane := range p.LaneStatus().Lanes {
|
||||
if lane.Site == site {
|
||||
return lane
|
||||
}
|
||||
}
|
||||
t.Fatalf("no %q lane in the snapshot", site)
|
||||
return LaneState{}
|
||||
}
|
||||
|
||||
// When one browser Lane loses the sidecar, the round's remaining browser
|
||||
// Lanes are skipped: every fetch would fail anyway, and their Series must not
|
||||
// burn their stamps on a dead Chrome (issue #100).
|
||||
@@ -1459,7 +1441,7 @@ func TestRunOnceUnreachableBrowserStopsBrowserLanes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The shared flag decays after refuseBackoff: the next round probes
|
||||
// The shared flag decays after RefuseBackoff: the next round probes
|
||||
// again. comix's Series is resting (stamped last round), so the probe
|
||||
// falls to kagane — the only Lane with something due — and its fresh
|
||||
// loss re-gates the Lanes behind it.
|
||||
@@ -1605,19 +1587,21 @@ func TestRunOnceClampWarningNamesTheSite(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The owner's admin page (issue #102) reads Lane state out of the poller.
|
||||
// Before any pass the snapshot is empty — a restart must render "no data
|
||||
// yet", not zeroes — and each pass records what it saw: the frozen clock,
|
||||
// the due count and the pace, with refusal backoff derived at snapshot time.
|
||||
func TestLaneStatus(t *testing.T) {
|
||||
// The Lanes page (issue #145) reads the durable pass log, so the poller's
|
||||
// only duty to it is that every return path writes its row; the snapshot it
|
||||
// used to mirror into memory is gone. A refusing pass still records why it
|
||||
// declined, and the latest row per Site is what the page renders — nothing
|
||||
// else is left to assert against here, because the page's seam moved into the
|
||||
// web layer (web_test.go, seeded-row tests).
|
||||
func TestPassLogIsTheLanesPagesOnlyWindow(t *testing.T) {
|
||||
s, _ := newTestStore(t)
|
||||
now := time.UnixMilli(5_000_000)
|
||||
p := newTestPoller(t, s, &fakeFetcher{body: asuraSeriesFixture, status: 200}, now)
|
||||
|
||||
if st := p.LaneStatus(); len(st.Lanes) != 0 {
|
||||
t.Fatalf("lanes before any pass = %d, want 0 (nothing has run)", len(st.Lanes))
|
||||
} else if st.BrowserConfigured || st.BrowserReachable {
|
||||
t.Fatalf("browser before any pass = configured=%v reachable=%v, want false without a browser fetcher", st.BrowserConfigured, st.BrowserReachable)
|
||||
// No pass has run: the log is empty, which the page renders as "none
|
||||
// observed" rather than confident zeroes.
|
||||
if _, ok, err := s.LatestLanePass("asura"); err != nil || ok {
|
||||
t.Fatalf("LatestLanePass before any pass = ok %v err %v, want no row", ok, err)
|
||||
}
|
||||
|
||||
seedForCheck(t, s, "asura:chronicles", "https://asurascans.com/series/chronicles", 0)
|
||||
@@ -1630,66 +1614,33 @@ func TestLaneStatus(t *testing.T) {
|
||||
}
|
||||
p.runOnce(context.Background())
|
||||
|
||||
st := p.LaneStatus()
|
||||
var asura, kagane LaneState
|
||||
for _, lane := range st.Lanes {
|
||||
switch lane.Site {
|
||||
case "asura":
|
||||
asura = lane
|
||||
case "kagane":
|
||||
kagane = lane
|
||||
}
|
||||
// Both lanes wrote their rows: asura the pass it read; kagane the
|
||||
// mid-loop double-refusal exit, which records an empty skip by design
|
||||
// with its two refused counts — the durable row is the whole record, and
|
||||
// the page reads it rather than a snapshot.
|
||||
asura := latestPassFor(t, s, "asura")
|
||||
if asura.Due != 1 || asura.Checked != 1 || asura.GapMS == 0 {
|
||||
t.Fatalf("asura row = %+v, want due 1 checked 1 with the Lane's pace", asura)
|
||||
}
|
||||
if st.Lanes[0].Site != "asura" {
|
||||
t.Fatalf("first lane = %q, want asura (snapshot sorted by Site)", st.Lanes[0].Site)
|
||||
}
|
||||
if asura.Site == "" {
|
||||
t.Fatalf("asura missing from snapshot: %+v", st.Lanes)
|
||||
}
|
||||
if !asura.LastRun.Equal(now) {
|
||||
t.Fatalf("asura LastRun = %s, want the frozen clock %s", asura.LastRun, now)
|
||||
}
|
||||
if asura.Due != 1 {
|
||||
t.Fatalf("asura Due = %d, want 1", asura.Due)
|
||||
}
|
||||
if asura.Gap == 0 {
|
||||
t.Fatal("asura Gap = 0, want the Lane's pace")
|
||||
}
|
||||
if asura.Browser || asura.Refusing {
|
||||
t.Fatalf("asura = %+v, want a TLS Lane that is not refusing", asura)
|
||||
}
|
||||
if !kagane.Browser || !kagane.Refusing {
|
||||
t.Fatalf("kagane = %+v, want a browser Lane in refusal backoff", kagane)
|
||||
}
|
||||
if !st.BrowserConfigured || !st.BrowserReachable {
|
||||
t.Fatalf("browser after round = configured=%v reachable=%v, want true/true (sidecar never lost)", st.BrowserConfigured, st.BrowserReachable)
|
||||
}
|
||||
if asura.Checked != 1 {
|
||||
t.Fatalf("asura Checked = %d, want the one Series it read", asura.Checked)
|
||||
kagane := latestPassFor(t, s, "kagane")
|
||||
if kagane.Skip != "" || kagane.Refused != 2 || kagane.Due != 2 || kagane.GapMS == 0 {
|
||||
t.Fatalf("kagane row = %+v, want an empty-skip double-refusal pass with 2 refused", kagane)
|
||||
}
|
||||
|
||||
// A pass that declines to look (kagane is now in backoff) must not restate
|
||||
// the figures it never gathered as zeroes: the last real pass's due count
|
||||
// and pace stand until a pass replaces them.
|
||||
// With the refusal now durable, the next pass declines ahead of the loop:
|
||||
// it records the refusing skip and carries the previous pass's figures
|
||||
// forward rather than restating zeroes it never gathered (the carry logic
|
||||
// itself is driven in TestRunLanePassCarryForwardOnlyWhenGapZero).
|
||||
before := kagane
|
||||
if before.Due == 0 || before.Gap == 0 {
|
||||
t.Fatalf("kagane after its refusing pass = %+v, want the figures that pass gathered", before)
|
||||
}
|
||||
p.Now = func() time.Time { return now.Add(time.Minute) }
|
||||
p.runOnce(context.Background())
|
||||
for _, lane := range p.LaneStatus().Lanes {
|
||||
if lane.Site != "kagane" {
|
||||
continue
|
||||
}
|
||||
if lane.Due != before.Due || lane.Gap != before.Gap {
|
||||
t.Fatalf("kagane after a skipped pass = due %d gap %s, want the previous pass's %d / %s",
|
||||
lane.Due, lane.Gap, before.Due, before.Gap)
|
||||
}
|
||||
again := latestPassFor(t, s, "kagane")
|
||||
if again.Skip != SkipRefusing {
|
||||
t.Fatalf("kagane refusing pass skip = %q, want %q", again.Skip, SkipRefusing)
|
||||
}
|
||||
|
||||
// A lost sidecar reads as unreachable for the same window the Lanes skip.
|
||||
p.setBrowserDown(now)
|
||||
if st := p.LaneStatus(); !st.BrowserConfigured || st.BrowserReachable {
|
||||
t.Fatalf("browser after loss = configured=%v reachable=%v, want true/false", st.BrowserConfigured, st.BrowserReachable)
|
||||
if again.Due != before.Due || again.GapMS != before.GapMS || again.Checked != before.Checked {
|
||||
t.Fatalf("kagane after a skipped pass = due %d gap %d, want the previous pass's %d / %d",
|
||||
again.Due, again.GapMS, before.Due, before.GapMS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1744,8 +1695,8 @@ func TestRunLanePassRecordsEveryExit(t *testing.T) {
|
||||
if got := readLatestCheckedAt(t, s, "asura:x"); got != 0 {
|
||||
t.Fatalf("stamp while paused = %d, want 0 (Series stay due and unstamped)", got)
|
||||
}
|
||||
if pass := latestPassFor(t, s, "asura"); pass.Skip != skipPaused {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, skipPaused)
|
||||
if pass := latestPassFor(t, s, "asura"); pass.Skip != SkipPaused {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, SkipPaused)
|
||||
}
|
||||
if got := countPassRows(t, dbURL, "asura"); got != 1 {
|
||||
t.Fatalf("pass rows = %d, want exactly 1", got)
|
||||
@@ -1767,8 +1718,8 @@ func TestRunLanePassRecordsEveryExit(t *testing.T) {
|
||||
if browser.callCount() != 0 {
|
||||
t.Fatalf("fetches while refusing = %d, want 0", browser.callCount())
|
||||
}
|
||||
if pass := latestPassFor(t, s, "kagane"); pass.Skip != skipRefusing {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, skipRefusing)
|
||||
if pass := latestPassFor(t, s, "kagane"); pass.Skip != SkipRefusing {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, SkipRefusing)
|
||||
}
|
||||
if got := countPassRows(t, dbURL, "kagane"); got != 1 {
|
||||
t.Fatalf("pass rows = %d, want exactly 1", got)
|
||||
@@ -1782,14 +1733,14 @@ func TestRunLanePassRecordsEveryExit(t *testing.T) {
|
||||
p := newTestPoller(t, s, &fakeFetcher{status: 200}, now)
|
||||
p.BrowserFetch = browser
|
||||
p.setBrowserDown(now) // a sibling Lane lost Chrome within the backoff window
|
||||
if pace := p.runLanePass(context.Background(), "kagane", false); pace != refuseBackoff {
|
||||
t.Fatalf("sidecar-down pace = %s, want %s", pace, refuseBackoff)
|
||||
if pace := p.runLanePass(context.Background(), "kagane", false); pace != RefuseBackoff {
|
||||
t.Fatalf("sidecar-down pace = %s, want %s", pace, RefuseBackoff)
|
||||
}
|
||||
if browser.callCount() != 0 {
|
||||
t.Fatalf("fetches with the sidecar down = %d, want 0", browser.callCount())
|
||||
}
|
||||
if pass := latestPassFor(t, s, "kagane"); pass.Skip != skipSidecarDown {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, skipSidecarDown)
|
||||
if pass := latestPassFor(t, s, "kagane"); pass.Skip != SkipSidecarDown {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, SkipSidecarDown)
|
||||
}
|
||||
if got := countPassRows(t, dbURL, "kagane"); got != 1 {
|
||||
t.Fatalf("pass rows = %d, want exactly 1", got)
|
||||
@@ -1804,8 +1755,8 @@ func TestRunLanePassRecordsEveryExit(t *testing.T) {
|
||||
t.Fatalf("no-fetcher pace = %s, want %s", pace, defaultGap)
|
||||
}
|
||||
pass := latestPassFor(t, s, "comix")
|
||||
if pass.Skip != skipNoFetcher || pass.GapMS != defaultGap.Milliseconds() {
|
||||
t.Fatalf("no-fetcher pass = %+v, want skip %q with its own gap %s", pass, skipNoFetcher, defaultGap)
|
||||
if pass.Skip != SkipNoFetcher || pass.GapMS != defaultGap.Milliseconds() {
|
||||
t.Fatalf("no-fetcher pass = %+v, want skip %q with its own gap %s", pass, SkipNoFetcher, defaultGap)
|
||||
}
|
||||
if got := countPassRows(t, dbURL, "comix"); got != 1 {
|
||||
t.Fatalf("pass rows = %d, want exactly 1", got)
|
||||
@@ -1829,8 +1780,8 @@ func TestRunLanePassRecordsEveryExit(t *testing.T) {
|
||||
if pace := p.runLanePass(context.Background(), "asura", false); pace != defaultGap {
|
||||
t.Fatalf("due-query pace = %s, want %s", pace, defaultGap)
|
||||
}
|
||||
if pass := latestPassFor(t, s, "asura"); pass.Skip != skipDueQuery {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, skipDueQuery)
|
||||
if pass := latestPassFor(t, s, "asura"); pass.Skip != SkipDueQuery {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, SkipDueQuery)
|
||||
}
|
||||
if got := countPassRows(t, dbURL, "asura"); got != 1 {
|
||||
t.Fatalf("pass rows = %d, want exactly 1", got)
|
||||
@@ -1853,8 +1804,8 @@ func TestRunLanePassRecordsEveryExit(t *testing.T) {
|
||||
t.Fatalf("fetches while Chrome is asleep = %d, want 0", browser.callCount())
|
||||
}
|
||||
pass := latestPassFor(t, s, "kagane")
|
||||
if pass.Skip != skipAsleep || pass.Due != 3 || pass.GapMS != defaultGap.Milliseconds() {
|
||||
t.Fatalf("asleep pass = %+v, want skip %q with 3 due and the default gap", pass, skipAsleep)
|
||||
if pass.Skip != SkipAsleep || pass.Due != 3 || pass.GapMS != defaultGap.Milliseconds() {
|
||||
t.Fatalf("asleep pass = %+v, want skip %q with 3 due and the default gap", pass, SkipAsleep)
|
||||
}
|
||||
if got := countPassRows(t, dbURL, "kagane"); got != 1 {
|
||||
t.Fatalf("pass rows = %d, want exactly 1", got)
|
||||
@@ -1871,8 +1822,8 @@ func TestRunLanePassRecordsEveryExit(t *testing.T) {
|
||||
if pace := p.runLanePass(context.Background(), "asura", false); pace != defaultGap {
|
||||
t.Fatalf("eligible-count pace = %s, want %s", pace, defaultGap)
|
||||
}
|
||||
if pass := latestPassFor(t, s, "asura"); pass.Skip != skipEligibleCount {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, skipEligibleCount)
|
||||
if pass := latestPassFor(t, s, "asura"); pass.Skip != SkipEligibleCount {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, SkipEligibleCount)
|
||||
}
|
||||
if got := countPassRows(t, dbURL, "asura"); got != 1 {
|
||||
t.Fatalf("pass rows = %d, want exactly 1", got)
|
||||
@@ -1885,8 +1836,8 @@ func TestRunLanePassRecordsEveryExit(t *testing.T) {
|
||||
if pace := p.runLanePass(context.Background(), "asura", false); pace != sites["asura"].Rest {
|
||||
t.Fatalf("nothing-eligible pace = %s, want a full rest %s", pace, sites["asura"].Rest)
|
||||
}
|
||||
if pass := latestPassFor(t, s, "asura"); pass.Skip != skipNothingEligible {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, skipNothingEligible)
|
||||
if pass := latestPassFor(t, s, "asura"); pass.Skip != SkipNothingEligible {
|
||||
t.Fatalf("skip = %q, want %q", pass.Skip, SkipNothingEligible)
|
||||
}
|
||||
if got := countPassRows(t, dbURL, "asura"); got != 1 {
|
||||
t.Fatalf("pass rows = %d, want exactly 1", got)
|
||||
@@ -1959,8 +1910,8 @@ func TestRunLanePassCarryForwardOnlyWhenGapZero(t *testing.T) {
|
||||
p.Now = func() time.Time { return now.Add(time.Minute) }
|
||||
p.runLanePass(context.Background(), "kagane", false)
|
||||
second := latestPassFor(t, s, "kagane")
|
||||
if second.Skip != skipRefusing {
|
||||
t.Fatalf("second pass skip = %q, want %q", second.Skip, skipRefusing)
|
||||
if second.Skip != SkipRefusing {
|
||||
t.Fatalf("second pass skip = %q, want %q", second.Skip, SkipRefusing)
|
||||
}
|
||||
if second.Due != first.Due || second.Checked != first.Checked ||
|
||||
second.GapMS != first.GapMS || second.Clamped != first.Clamped {
|
||||
@@ -1986,8 +1937,8 @@ func TestRunLanePassCarryForwardOnlyWhenGapZero(t *testing.T) {
|
||||
p.Now = func() time.Time { return now.Add(time.Minute) }
|
||||
p.runLanePass(context.Background(), "comix", false)
|
||||
second := latestPassFor(t, s, "comix")
|
||||
if second.Skip != skipNoFetcher {
|
||||
t.Fatalf("second pass skip = %q, want %q", second.Skip, skipNoFetcher)
|
||||
if second.Skip != SkipNoFetcher {
|
||||
t.Fatalf("second pass skip = %q, want %q", second.Skip, SkipNoFetcher)
|
||||
}
|
||||
if second.GapMS != defaultGap.Milliseconds() {
|
||||
t.Fatalf("second pass gap = %d, want its own %d (not carried)", second.GapMS, defaultGap.Milliseconds())
|
||||
@@ -2089,8 +2040,8 @@ func TestDurableRefusalSurvivesFreshPoller(t *testing.T) {
|
||||
if got := fresh.BrowserFetch.(*fakeFetcher).callCount(); got != 0 {
|
||||
t.Fatalf("fetches by a fresh poller inside the backoff = %d, want 0", got)
|
||||
}
|
||||
if pass := latestPassFor(t, s, "kagane"); pass.Skip != skipRefusing {
|
||||
t.Fatalf("fresh poller's pass skip = %q, want %q", pass.Skip, skipRefusing)
|
||||
if pass := latestPassFor(t, s, "kagane"); pass.Skip != SkipRefusing {
|
||||
t.Fatalf("fresh poller's pass skip = %q, want %q", pass.Skip, SkipRefusing)
|
||||
}
|
||||
|
||||
// Past the backoff the fresh poller probes again — the two Series the
|
||||
|
||||
Reference in New Issue
Block a user