Lengthen the WHOIS fetcher's wait after a skipped lookup (closes #222)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
A lookup whose RIR is paused is skipped. The skip left the fetcher's wait as it was. Each lookup asks IANA before it finds its RIR paused. So while most stale ASes belonged to a paused RIR, the fetcher could ask IANA every second. Like a failure, a skipped lookup now makes the wait 1.5 times as long, up to one minute. The skip is still not counted as a failure, so the status page's failure figures leave it out. Model: opus-5-5
This commit was merged in pull request #223.
This commit is contained in:
@@ -35,6 +35,12 @@ tested.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-11-02: like a failed WHOIS lookup, a lookup skipped because its RIR is
|
||||
paused makes the fetcher wait longer before the next lookup. The longest wait
|
||||
is one minute. The status page does not count a skip as a failure. A skip used
|
||||
to leave the wait as it was. Each lookup asks IANA before it finds its RIR
|
||||
paused. So while most stale ASes belonged to a paused RIR, the fetcher could
|
||||
ask IANA every second (closes #222)
|
||||
- 2026-11-02: a peer's session going down no longer stops the prefix handler.
|
||||
The handler runs the table's `PeerDown` on a separate goroutine, one peer at a
|
||||
time, and goes on taking messages. `PeerDown` holds the table's lock for
|
||||
|
||||
@@ -187,25 +187,33 @@ func (f *ASNFetcher) recordSuccess() {
|
||||
f.statsMu.Unlock()
|
||||
}
|
||||
|
||||
// recordFailure increases the interval on failed fetch using exponential backoff.
|
||||
func (f *ASNFetcher) recordFailure() {
|
||||
// lengthenInterval makes the interval 1.5 times as long, up to maxInterval.
|
||||
// A failed lookup and a skipped one both lengthen it.
|
||||
func (f *ASNFetcher) lengthenInterval() {
|
||||
f.intervalMu.Lock()
|
||||
f.consecutiveFails++
|
||||
defer f.intervalMu.Unlock()
|
||||
|
||||
// Exponential backoff: multiply by 1.5, capped at max
|
||||
newInterval := min(
|
||||
f.currentInterval*backoffNumerator/backoffDenominator, maxInterval)
|
||||
|
||||
if newInterval != f.currentInterval {
|
||||
f.logger.Debug("Increased fetch interval due to failure",
|
||||
f.logger.Debug("Increased fetch interval",
|
||||
"old_interval", f.currentInterval,
|
||||
"new_interval", newInterval,
|
||||
"consecutive_failures", f.consecutiveFails,
|
||||
)
|
||||
f.currentInterval = newInterval
|
||||
}
|
||||
}
|
||||
|
||||
// recordFailure counts a failed fetch and lengthens the interval.
|
||||
func (f *ASNFetcher) recordFailure() {
|
||||
f.intervalMu.Lock()
|
||||
f.consecutiveFails++
|
||||
f.intervalMu.Unlock()
|
||||
|
||||
f.lengthenInterval()
|
||||
|
||||
// Record error time for stats, dropping those older than statsWindow
|
||||
f.statsMu.Lock()
|
||||
now := f.now()
|
||||
@@ -291,9 +299,12 @@ func (f *ASNFetcher) fetchNextStale(ctx context.Context) {
|
||||
// The WHOIS client pauses an RIR's or NIC.br's server for whois.PauseTime
|
||||
// after the server refuses a query. Lookups at the other servers go on. IANA
|
||||
// is never paused, so a refusal by IANA is an ordinary failure. A lookup
|
||||
// whose RIR is paused fails with whois.ErrPaused. Such a lookup leaves the
|
||||
// AS stale, and counts as neither a success nor a failure. The lookups that
|
||||
// QueueImmediate asks for also run through fetchAndUpdate.
|
||||
// whose RIR is paused fails with whois.ErrPaused and is skipped. A skipped
|
||||
// lookup leaves the AS stale and is not counted as a failure. Like a
|
||||
// failure, a skipped lookup lengthens the interval. Each lookup asks IANA
|
||||
// before it finds its RIR paused, so with a one-second interval the fetcher
|
||||
// would ask IANA every second. The lookups that QueueImmediate asks for also
|
||||
// run through fetchAndUpdate.
|
||||
func (f *ASNFetcher) fetchAndUpdate(ctx context.Context, asn int) bool {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
@@ -317,6 +328,7 @@ func (f *ASNFetcher) fetchAndUpdate(ctx context.Context, asn int) bool {
|
||||
if errors.Is(err, whois.ErrPaused) {
|
||||
f.logger.Info("Skipped WHOIS lookup, the RIR's server is paused",
|
||||
"asn", asn, "error", err)
|
||||
f.lengthenInterval()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -196,6 +196,48 @@ func TestFetchAndUpdateWaitsOutAPause(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetchAndUpdateBacksOffDuringAPause looks up an AS at RIPE while RIPE
|
||||
// refuses the query. The refusal pauses RIPE for an hour, so every later
|
||||
// lookup in the test is skipped. The test sets the WHOIS client's clock and
|
||||
// looks the AS up again each time the interval has passed. Like a failure,
|
||||
// each skip must make the interval 1.5 times as long, up to one minute. The
|
||||
// skips must not count as failures.
|
||||
func TestFetchAndUpdateBacksOffDuringAPause(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f, _ := newFetcherWithWHOISAnswers(t, map[string]string{
|
||||
ianaWHOISServer: ianaRefersToRIPE,
|
||||
ripeWHOISServer: ripeAccessDenied,
|
||||
})
|
||||
|
||||
now := time.Date(2026, 11, 2, 0, 0, 0, 0, time.UTC)
|
||||
f.whoisClient.Now = func() time.Time { return now }
|
||||
|
||||
f.fetchAndUpdate(t.Context(), testASNA)
|
||||
|
||||
for i, want := range []time.Duration{
|
||||
33750 * time.Millisecond,
|
||||
50625 * time.Millisecond,
|
||||
time.Minute,
|
||||
time.Minute,
|
||||
} {
|
||||
now = now.Add(f.getInterval())
|
||||
|
||||
f.fetchAndUpdate(t.Context(), testASNA)
|
||||
|
||||
if got := f.getInterval(); got != want {
|
||||
t.Errorf("interval after %d skipped lookups = %v, want %v",
|
||||
i+1, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
got := f.GetStats()
|
||||
if got.ConsecutiveFails != 1 || got.ErrorsLastHour != 1 {
|
||||
t.Errorf("%d failures in a row and %d errors, want 1 and 1",
|
||||
got.ConsecutiveFails, got.ErrorsLastHour)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetchAndUpdateKeepsAskingIANA looks up an AS while IANA's server
|
||||
// refuses the connection. The test sets the WHOIS client's clock. The next
|
||||
// lookup comes a second before whois.PauseTime has passed. IANA is never
|
||||
|
||||
Reference in New Issue
Block a user