naco-api/internal/activitypub/throttle.go
Nacorid c96447e3e0
fix(activitypub): harden client, add outbox fallback and throttled GTS warming
Extract ActivityPub outbox walk into internal/activitypub/outbox.go with
webfinger/actor resolution, Create/Note filtering and IRI dereferencing.
Fix searchStatus returning wrong post, nil-account panic in SearchActors,
@local handle resolution, empty Author via noteToInternalPost, protocol-
relative URL handling, pagination loops, limit clamping (200) and cacheErr
masking. Add per-host token-bucket throttle (1/s, cross-host concurrency)
in throttle.go and background GTS warming via search?resolve for outbox-
fetched posts without following remotes. Align AGENTS.md.

Co-Authored-By: internal-model
2026-09-10 09:59:44 +00:00

58 lines
922 B
Go

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()
}
}