package activitypub import ( "context" "sync" "time" ) type hostLimiter struct { mu sync.Mutex buckets map[string]*bucket } type bucket struct { mu sync.Mutex last time.Time } func newHostLimiter() *hostLimiter { return &hostLimiter{buckets: make(map[string]*bucket)} } func (h *hostLimiter) Wait(ctx context.Context, host string) error { h.mu.Lock() b, ok := h.buckets[host] if !ok { b = &bucket{} h.buckets[host] = b if len(h.buckets) > 1024 { for k, v := range h.buckets { if time.Since(v.last) > 10*time.Minute { delete(h.buckets, k) } } } } h.mu.Unlock() b.mu.Lock() defer b.mu.Unlock() if b.last.IsZero() { b.last = time.Now() return nil } wait := time.Until(b.last.Add(time.Second)) if wait <= 0 { b.last = time.Now() return nil } select { case <-time.After(wait): b.last = time.Now() return nil case <-ctx.Done(): return ctx.Err() } }