package eh // 全局限流器 // // 算法对齐参考实现(E-Hentai Downloader 的「请求间隔」设置): // 用一个全局的 nextRequestTime,每次请求都把自己排到它后面,再把它往后推。 // // next = max(next, now) + interval // 等待到 next 时刻再真正发出 // // 效果是「所有请求之间至少间隔 interval 毫秒」,与并发数无关 —— // 哪怕 40 个 worker 同时来,也会被串成每 interval 放行一个。 // // interval = 0 时完全旁路,不产生任何额外开销。 import ( "context" "sync" "time" ) // Limiter 全局请求节流器 type Limiter struct { mu sync.Mutex interval time.Duration next time.Time // 下一个请求允许发出的时刻 } // NewLimiter 建限流器(interval <= 0 表示不限流) func NewLimiter(interval time.Duration) *Limiter { if interval < 0 { interval = 0 } return &Limiter{interval: interval} } // SetInterval 运行时改间隔 func (l *Limiter) SetInterval(d time.Duration) { if d < 0 { d = 0 } l.mu.Lock() l.interval = d l.mu.Unlock() } // Interval 当前间隔 func (l *Limiter) Interval() time.Duration { l.mu.Lock() defer l.mu.Unlock() return l.interval } // Wait 占用一个请求名额,必要时阻塞到放行时刻。 // 返回 error 是为了能响应取消(点「停止」时不该继续排队)。 func (l *Limiter) Wait(ctx context.Context) error { l.mu.Lock() interval := l.interval if interval <= 0 { l.mu.Unlock() return ctx.Err() } now := time.Now() // 关键:如果过去的排队已经堆积,next 可能远在未来, // 这里取 max 让第一个请求立刻发出去,避免冷启动时集体等一大轮。 if l.next.Before(now) { l.next = now } at := l.next l.next = at.Add(interval) l.mu.Unlock() delay := time.Until(at) if delay <= 0 { return ctx.Err() } t := time.NewTimer(delay) defer t.Stop() select { case <-ctx.Done(): return ctx.Err() case <-t.C: return nil } }