mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-24 11:57:15 +00:00
fix(node): stop a departed master's frozen traffic from disabling clients (#6113)
client_global_traffics rows are keyed by (master_guid, email) and are only
ever overwritten by a push from that same master. A master that stops
pushing — decommissioned, reinstalled under a fresh GUID, or detached from
the node — therefore leaves its last snapshot behind permanently.
depletedClientsCond's cross-panel EXISTS branch matched any such row, so a
node kept comparing a client's quota against counters frozen weeks earlier.
Once they exceeded the quota the node disabled the client on every traffic
poll, and the node -> master enable merge latched that off on the master too,
where nothing sets it back. The reported symptom is exactly this: a client at
11 GB of a 24 GB quota, enabled on two nodes, disabled on the third, which
still held a 27-day-old row from a previous master reporting 30 GB.
Bound both the enforcement predicate and the display overlay to rows a master
refreshed within globalTrafficFreshWindow. Masters push every 30s, so a live
master is never affected; a master that is merely unreachable for a while
keeps enforcing for a full day before its numbers are set aside.
The one-way enable merge that makes such a disable permanent on the master is
deliberate (12d84c2a, #4917) and is left alone.
This commit is contained in:
@@ -100,15 +100,21 @@ func chunkGlobalRows(rows []model.ClientGlobalTraffic, size int) [][]model.Clien
|
||||
}
|
||||
|
||||
// overlayGlobalTraffic raises Up/Down on the given rows to the largest global
|
||||
// value any master pushed for that email. Read-path only — callers hand it
|
||||
// rows about to be serialized for display; the stored counters are untouched.
|
||||
// value any master still pushing for that email reported. Read-path only —
|
||||
// callers hand it rows about to be serialized for display; the stored counters
|
||||
// are untouched. Rows older than globalTrafficFreshWindow are ignored: they
|
||||
// come from a master that stopped pushing, and folding their frozen counters
|
||||
// in would keep showing usage the client no longer has (#6113).
|
||||
func overlayGlobalTraffic(db *gorm.DB, rows []*xray.ClientTraffic) {
|
||||
if len(rows) == 0 {
|
||||
return
|
||||
}
|
||||
freshSince := globalTrafficFreshSince()
|
||||
// Cheap short-circuit for the common case (a panel no master pushes to).
|
||||
var probe int64
|
||||
if err := db.Model(&model.ClientGlobalTraffic{}).Limit(1).Count(&probe).Error; err != nil || probe == 0 {
|
||||
if err := db.Model(&model.ClientGlobalTraffic{}).
|
||||
Where("updated_at >= ?", freshSince).
|
||||
Limit(1).Count(&probe).Error; err != nil || probe == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -126,7 +132,7 @@ func overlayGlobalTraffic(db *gorm.DB, rows []*xray.ClientTraffic) {
|
||||
}
|
||||
for _, batch := range chunkStrings(emails, sqlInChunk) {
|
||||
var globals []model.ClientGlobalTraffic
|
||||
if err := db.Where("email IN ?", batch).Find(&globals).Error; err != nil {
|
||||
if err := db.Where("email IN ? AND updated_at >= ?", batch, freshSince).Find(&globals).Error; err != nil {
|
||||
logger.Warning("overlayGlobalTraffic:", err)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user