Re-vendor the shared files from sneak/prompts at 0653579 (closes #52)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
Takes .golangci.yml, .dockerignore, .gitignore, .editorconfig, the workflow and REPO_POLICIES.md from sneak/prompts 0653579, with this repository's own entries after the shared content. Lint (golangci-lint v2.14.0) and test are Dockerfile phases that script/lint and script/test build alone with a new CACHEBUST value, keeping Go's and golangci-lint's caches; the image build depends on both. make fmt and make fmt-check also format the markdown with prettier, pinned in yarn.lock. The agent guidance moves to AGENTS.md. The Go code is fixed for what the new configuration finds, and the AS and prefix detail reads now return the rows' error after their loops. Model: opus-5-5
This commit was merged in pull request #85.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
// Package routewatch contains the ASN WHOIS fetcher for background updates.
|
||||
package routewatch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -73,10 +73,13 @@ func NewASNFetcher(db database.Store, logger *slog.Logger) *ASNFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
// Start begins the background ASN fetcher goroutine.
|
||||
func (f *ASNFetcher) Start() {
|
||||
// Start begins the background ASN fetcher goroutine. It runs until Stop:
|
||||
// the end of ctx does not stop it.
|
||||
func (f *ASNFetcher) Start(ctx context.Context) {
|
||||
f.wg.Add(1)
|
||||
go f.run()
|
||||
|
||||
go f.run(context.WithoutCancel(ctx))
|
||||
|
||||
f.logger.Info("ASN fetcher started",
|
||||
"base_interval", baseInterval,
|
||||
"min_interval", minInterval,
|
||||
@@ -151,10 +154,7 @@ func (f *ASNFetcher) recordSuccess() {
|
||||
f.consecutiveFails = 0
|
||||
|
||||
// Decrease interval by half, but not below minimum
|
||||
newInterval := f.currentInterval / backoffMultiplier
|
||||
if newInterval < minInterval {
|
||||
newInterval = minInterval
|
||||
}
|
||||
newInterval := max(f.currentInterval/backoffMultiplier, minInterval)
|
||||
|
||||
if newInterval != f.currentInterval {
|
||||
f.logger.Debug("Decreased fetch interval",
|
||||
@@ -177,10 +177,7 @@ func (f *ASNFetcher) recordFailure() {
|
||||
f.consecutiveFails++
|
||||
|
||||
// Exponential backoff: multiply by 2, capped at max
|
||||
newInterval := f.currentInterval * backoffMultiplier
|
||||
if newInterval > maxInterval {
|
||||
newInterval = maxInterval
|
||||
}
|
||||
newInterval := min(f.currentInterval*backoffMultiplier, maxInterval)
|
||||
|
||||
if newInterval != f.currentInterval {
|
||||
f.logger.Debug("Increased fetch interval due to failure",
|
||||
@@ -199,7 +196,7 @@ func (f *ASNFetcher) recordFailure() {
|
||||
}
|
||||
|
||||
// run is the main background loop.
|
||||
func (f *ASNFetcher) run() {
|
||||
func (f *ASNFetcher) run(ctx context.Context) {
|
||||
defer f.wg.Done()
|
||||
|
||||
timer := time.NewTimer(f.getInterval())
|
||||
@@ -212,13 +209,13 @@ func (f *ASNFetcher) run() {
|
||||
|
||||
case asn := <-f.immediateQueue:
|
||||
// Process immediate request (respects lock)
|
||||
f.tryFetch(asn)
|
||||
f.tryFetch(ctx, asn)
|
||||
// Reset timer after immediate fetch
|
||||
timer.Reset(f.getInterval())
|
||||
|
||||
case <-timer.C:
|
||||
// Background fetch of stale/missing ASN
|
||||
f.fetchNextStale()
|
||||
f.fetchNextStale(ctx)
|
||||
// Reset timer with potentially updated interval
|
||||
timer.Reset(f.getInterval())
|
||||
}
|
||||
@@ -227,7 +224,7 @@ func (f *ASNFetcher) run() {
|
||||
|
||||
// tryFetch attempts to fetch and update an ASN, respecting the fetch lock.
|
||||
// Returns true if fetch was successful.
|
||||
func (f *ASNFetcher) tryFetch(asn int) bool {
|
||||
func (f *ASNFetcher) tryFetch(ctx context.Context, asn int) bool {
|
||||
// Try to acquire lock, skip if another fetch is running
|
||||
if !f.fetchMu.TryLock() {
|
||||
f.logger.Debug("Skipping fetch, another fetch in progress", "asn", asn)
|
||||
@@ -236,11 +233,11 @@ func (f *ASNFetcher) tryFetch(asn int) bool {
|
||||
}
|
||||
defer f.fetchMu.Unlock()
|
||||
|
||||
return f.fetchAndUpdate(asn)
|
||||
return f.fetchAndUpdate(ctx, asn)
|
||||
}
|
||||
|
||||
// fetchNextStale finds and fetches the next ASN needing WHOIS data.
|
||||
func (f *ASNFetcher) fetchNextStale() {
|
||||
func (f *ASNFetcher) fetchNextStale(ctx context.Context) {
|
||||
// Try to acquire lock, skip if another fetch is running
|
||||
if !f.fetchMu.TryLock() {
|
||||
f.logger.Debug("Skipping stale fetch, another fetch in progress")
|
||||
@@ -249,12 +246,12 @@ func (f *ASNFetcher) fetchNextStale() {
|
||||
}
|
||||
defer f.fetchMu.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
lookupCtx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
|
||||
asn, err := f.db.GetNextStaleASN(ctx, whoisStaleThreshold)
|
||||
asn, err := f.db.GetNextStaleASN(lookupCtx, whoisStaleThreshold)
|
||||
if err != nil {
|
||||
if err != database.ErrNoStaleASN {
|
||||
if !errors.Is(err, database.ErrNoStaleASN) {
|
||||
f.logger.Error("Failed to get stale ASN", "error", err)
|
||||
f.recordFailure()
|
||||
}
|
||||
@@ -263,13 +260,13 @@ func (f *ASNFetcher) fetchNextStale() {
|
||||
return
|
||||
}
|
||||
|
||||
f.fetchAndUpdate(asn)
|
||||
f.fetchAndUpdate(ctx, asn)
|
||||
}
|
||||
|
||||
// fetchAndUpdate performs a WHOIS lookup and updates the database.
|
||||
// Returns true if successful.
|
||||
func (f *ASNFetcher) fetchAndUpdate(asn int) bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
func (f *ASNFetcher) fetchAndUpdate(ctx context.Context, asn int) bool {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
|
||||
f.logger.Info("Fetching WHOIS data", "asn", asn)
|
||||
|
||||
Reference in New Issue
Block a user