mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-12 06:10:58 +00:00
fix(node): stop Postgres deadlocks and deleted-client resurrection in node sync
Two defects in the node traffic sync, both hit hard on busy
master+multi-node Postgres deployments:
Client-IP merges deadlocked. Each node syncs on its own goroutine and
shared clients appear in several nodes' reports, but MergeInboundClientIps
and upsertNodeClientIps locked rows in whatever order each node's report
arrived. Two concurrent merges taking the same rows in opposite order is
exactly what Postgres aborts with SQLSTATE 40P01 ("merge client ips from
<node> failed: deadlock detected"). Both merges now process emails in
sorted order so every transaction acquires row locks in one global order.
Deleted clients resurrected with zeroed traffic. A snapshot fetched just
before a deletion still names the deleted email; applying it after the
delete committed re-added the client. The delete tombstone existed for
precisely this race but only zeroed the seed counters: the sync still
recreated the client_traffics row, and worse, adopted the node's stale
settings JSON wholesale, putting the client back in the central inbound
as if it were brand new with 0 traffic. Snapshot application now skips
row creation for tombstoned emails on known inbounds and strips
tombstoned clients from adopted settings; fresh node-adoption semantics
(rows seeded at zero) are unchanged.
The mass-disconnect part of the report is the forced node restart on
auto-disable, removed separately in 4d6f2ddd.
Closes #5739
This commit is contained in:
@@ -486,6 +486,14 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
|
||||
inGrace := c.LastTrafficResetTime > 0 && now-c.LastTrafficResetTime < resetGracePeriodMs
|
||||
|
||||
// Adopting the node's settings verbatim would re-add a client the master
|
||||
// deleted moments ago if this snapshot was fetched before the deletion
|
||||
// push landed — filter just-deleted emails out while their tombstone lives.
|
||||
adoptedSettings := snapIb.Settings
|
||||
if stripped, changed := stripTombstonedClients(adoptedSettings); changed {
|
||||
adoptedSettings = stripped
|
||||
}
|
||||
|
||||
updates := map[string]any{}
|
||||
if !dirty {
|
||||
updates["enable"] = snapIb.Enable
|
||||
@@ -496,7 +504,7 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
updates["protocol"] = snapIb.Protocol
|
||||
updates["total"] = snapIb.Total
|
||||
updates["expiry_time"] = snapIb.ExpiryTime
|
||||
updates["settings"] = snapIb.Settings
|
||||
updates["settings"] = adoptedSettings
|
||||
updates["stream_settings"] = snapIb.StreamSettings
|
||||
updates["sniffing"] = snapIb.Sniffing
|
||||
updates["traffic_reset"] = snapIb.TrafficReset
|
||||
@@ -513,7 +521,7 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
updates["origin_node_guid"] = og
|
||||
}
|
||||
|
||||
if !dirty && (c.Settings != snapIb.Settings ||
|
||||
if !dirty && (c.Settings != adoptedSettings ||
|
||||
c.Remark != snapIb.Remark ||
|
||||
c.Listen != snapIb.Listen ||
|
||||
c.Port != snapIb.Port ||
|
||||
@@ -634,8 +642,17 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
if dirty {
|
||||
continue
|
||||
}
|
||||
_, isNewInbound := newInboundIDs[c.Id]
|
||||
// On a known inbound a missing row plus a live tombstone means the
|
||||
// master just deleted this client and the snapshot predates the
|
||||
// deletion push — recreating the row (at zero) would resurrect the
|
||||
// client. A freshly adopted inbound still gets its row (seeded at
|
||||
// zero) so adoption semantics stay intact.
|
||||
if !isNewInbound && isClientEmailTombstoned(cs.Email) {
|
||||
continue
|
||||
}
|
||||
var seedUp, seedDown int64
|
||||
if _, isNewInbound := newInboundIDs[c.Id]; isNewInbound && !isClientEmailTombstoned(cs.Email) {
|
||||
if isNewInbound && !isClientEmailTombstoned(cs.Email) {
|
||||
seedUp, seedDown = canon.Up, canon.Down
|
||||
}
|
||||
row := &xray.ClientTraffic{
|
||||
|
||||
Reference in New Issue
Block a user