diff --git a/internal/accounts/ways.go b/internal/accounts/ways.go index 9aa525c..122b0cf 100644 --- a/internal/accounts/ways.go +++ b/internal/accounts/ways.go @@ -390,7 +390,7 @@ type SetuidCache struct { mu sync.Mutex wg sync.WaitGroup - at time.Time // when the last search that finished finished + at time.Time // when the last search that finished was started, on the caller's clock found []string running bool startedAt time.Time @@ -431,8 +431,14 @@ const ( ReasonIncomplete = rootsearch.ReasonIncomplete ) -// Look answers the last result and when its search finished, starting a search in the background when one is +// Look answers the last result and when its search was started, starting a search in the background when one is // due; it never waits for one. Nil c searches in place, for a caller that wants to. +// +// **One clock: the caller's.** A search's time is the `now` it was started at, never the wall clock read when it +// finished: due-ness compares a later `now` with it, and mixing the two clocks made a search never due again +// whenever the caller's clock ran behind the wall clock — the test with a fixed date failed on every branch from +// 2026-10-09 18:00 UTC (found while checking issue 356). Measured from the start, a result reads at most one +// search older than it is, which errs towards searching again. func (c *SetuidCache) Look(now time.Time, search func(ctx context.Context) ([]string, error)) ([]string, time.Time, error) { if c == nil { ctx, cancel := context.WithTimeout(context.Background(), SearchBound) @@ -446,6 +452,7 @@ func (c *SetuidCache) Look(now time.Time, search func(ctx context.Context) ([]st (c.failed == nil || now.Sub(c.failedAt) >= c.backoff) if due { c.running, c.startedAt = true, now + started := now c.wg.Add(1) go func() { defer c.wg.Done() @@ -455,13 +462,12 @@ func (c *SetuidCache) Look(now time.Time, search func(ctx context.Context) ([]st c.mu.Lock() defer c.mu.Unlock() c.running = false - done := time.Now() if err != nil { - c.failed, c.failedAt = err, done + c.failed, c.failedAt = err, started c.backoff = min(max(2*c.backoff, c.Every), MaxBackoff) return } - c.found, c.at, c.failed, c.backoff = found, done, nil, 0 + c.found, c.at, c.failed, c.backoff = found, started, nil, 0 }() } switch {