Files
3x-ui-web-src/internal/eventbus/filter.go
T
Sanaei f727d04f65
CI / go-test (push) Canceled after 0s
CI / postgres-durable-first (push) Canceled after 0s
CI / codegen (push) Canceled after 0s
CI / govulncheck (push) Canceled after 0s
CI / race (push) Canceled after 0s
CI / fuzz-smoke (push) Canceled after 0s
CI / golangci (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CodeQL Advanced / Analyze (go) (push) Canceled after 0s
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
Docs CI / build (push) Canceled after 0s
Docs Deploy (GitHub Pages) / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-latest) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-latest) (push) Canceled after 0s
Docs Deploy (GitHub Pages) / deploy (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
v3.7.0
2026-08-24 15:07:15 +02:00

53 lines
1.3 KiB
Go

package eventbus
import (
"sync"
"time"
)
// RateLimiter prevents notification spam from flapping events.
type RateLimiter struct {
mu sync.Mutex
lastSent map[string]time.Time
cooldown time.Duration
lastPrune time.Time
}
// NewRateLimiter creates a rate limiter with the given cooldown period.
func NewRateLimiter(cooldown time.Duration) *RateLimiter {
return &RateLimiter{
lastSent: make(map[string]time.Time),
cooldown: cooldown,
}
}
// Allow returns true if the event should be sent (cooldown has elapsed).
func (r *RateLimiter) Allow(eventType EventType, source string) bool {
key := string(eventType) + ":" + source
now := time.Now()
r.mu.Lock()
defer r.mu.Unlock()
r.pruneLocked(now)
if now.Sub(r.lastSent[key]) < r.cooldown {
return false
}
r.lastSent[key] = now
return true
}
// pruneLocked drops keys whose cooldown has elapsed. Such an entry no longer
// affects Allow's result, so removing it is safe and keeps the map from
// retaining one entry per (eventType, source) ever seen. Throttled to once per
// cooldown so a busy bus doesn't sweep the whole map on every event.
func (r *RateLimiter) pruneLocked(now time.Time) {
if now.Sub(r.lastPrune) < r.cooldown {
return
}
r.lastPrune = now
for k, v := range r.lastSent {
if now.Sub(v) >= r.cooldown {
delete(r.lastSent, k)
}
}
}