// Package musicbrainz is Minstrel's client for the MusicBrainz web service: // searches and browses for the release catalogue (M502 #5519), and the one // pacer every MusicBrainz request in the process waits on. package musicbrainz import ( "context" "sync" "time" ) // MinInterval honours MusicBrainz's 1 request/second ceiling, with headroom. // The limit is per client address, not per caller, which is why there is one // pacer for the whole process. const MinInterval = 1100 * time.Millisecond // Pacer spaces requests at least Interval apart. Safe for concurrent use; // callers queue in the order they claim slots. type Pacer struct { Interval time.Duration mu sync.Mutex next time.Time } // Wait blocks until this caller's slot comes up, or ctx ends. A zero // Interval never waits. func (p *Pacer) Wait(ctx context.Context) error { if p == nil || p.Interval <= 0 { return ctx.Err() } p.mu.Lock() now := time.Now() slot := p.next if slot.Before(now) { slot = now } p.next = slot.Add(p.Interval) p.mu.Unlock() wait := time.Until(slot) if wait <= 0 { return ctx.Err() } timer := time.NewTimer(wait) defer timer.Stop() select { case <-ctx.Done(): return ctx.Err() case <-timer.C: return nil } } var shared = &Pacer{Interval: MinInterval} // Shared is the pacer every MusicBrainz request in the process goes through: // the catalogue's and tag enrichment's alike. func Shared() *Pacer { return shared }