mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-30 15:07:14 +00:00
perf: prevent cron job overlap, auto-set GOMEMLIMIT, fix tgbot userStates race
cron: SkipIfStillRunning stops a slow 5s/10s job from overlapping itself and racing the shared xrayAPI (grpc conn leak) and the StatsLastValues map (fatal concurrent map write). memlimit: auto-detect a Go soft memory limit from XUI_MEMORY_LIMIT, the cgroup limit, or system RAM (about 90 percent); opt-in pprof via XUI_PPROF. tgbot: userStates now goes through a mutex-guarded store with TTL pruning (was raced by worker-pool and delayed-delete goroutines). check_client_ip: prefilter inbounds by settings LIKE limitIp instead of loading and JSON-parsing all of them every scan. minor: prune StatsLastValues, RateLimiter.lastSent, reportedRemoteTagConflict. docker-compose: document the memory knobs.
This commit is contained in:
@@ -7,9 +7,10 @@ import (
|
||||
|
||||
// RateLimiter prevents notification spam from flapping events.
|
||||
type RateLimiter struct {
|
||||
mu sync.Mutex
|
||||
lastSent map[string]time.Time
|
||||
cooldown time.Duration
|
||||
mu sync.Mutex
|
||||
lastSent map[string]time.Time
|
||||
cooldown time.Duration
|
||||
lastPrune time.Time
|
||||
}
|
||||
|
||||
// NewRateLimiter creates a rate limiter with the given cooldown period.
|
||||
@@ -23,11 +24,29 @@ func NewRateLimiter(cooldown time.Duration) *RateLimiter {
|
||||
// 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()
|
||||
if time.Since(r.lastSent[key]) < r.cooldown {
|
||||
r.pruneLocked(now)
|
||||
if now.Sub(r.lastSent[key]) < r.cooldown {
|
||||
return false
|
||||
}
|
||||
r.lastSent[key] = time.Now()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user