mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-06 21:25:04 +03:00
fix(clients): stop client ops from reverting a renewal committed mid-op
Invariant: an operation on an inbound's clients writes back only what it changed, onto the settings as committed when it writes. Every client op (add, edit, delete, bulk adjust/detach/delete/set-enable) read the inbound outside the serial traffic writer and then tx.Save'd the whole row inside it. A traffic tick that committed in between - auto-renew re-enabling a client, the delayed-start conversion, a node adoption - was overwritten with the stale copy. A renewed neighbour ended up enable=false with its old expiry in settings while client_traffics said enabled, so the next runtime rebuild dropped a healthy client nobody had touched. The bulk ops then ran a full SyncInbound from those stale settings, copying enable=false into the client record too. Each site now commits through commitInboundClientSettings: inside the serialized tx it three-way merges the op's edit (read -> output, per client and per field) onto the committed settings and updates only the settings column, which also stops the stale up/down/enable inbound columns being written back. advancePushedInbound now records what the per-client push delivered rather than the merged settings, so the node's reconcile-skip fingerprint cannot claim a renewal it never received.
This commit is contained in:
@@ -795,6 +795,7 @@ func (s *ClientService) bulkAdjustInboundClients(
|
||||
}
|
||||
return res
|
||||
}
|
||||
prevSettings := oldInbound.Settings
|
||||
oldInbound.Settings = string(newSettings)
|
||||
|
||||
// A flow change rewrites the user's xray config, which the lightweight
|
||||
@@ -807,7 +808,7 @@ func (s *ClientService) bulkAdjustInboundClients(
|
||||
// Serialize against the traffic poll to avoid the cross-transaction
|
||||
// lock-order deadlock on inbounds/client_records (runSerializedTx).
|
||||
txErr := runSerializedTx(func(tx *gorm.DB) error {
|
||||
if err := tx.Save(oldInbound).Error; err != nil {
|
||||
if err := commitInboundClientSettings(tx, oldInbound, prevSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
finalClients, gcErr := inboundSvc.GetClients(oldInbound)
|
||||
@@ -1112,6 +1113,7 @@ func (s *ClientService) bulkDelInboundClients(
|
||||
}
|
||||
return res
|
||||
}
|
||||
prevSettings := oldInbound.Settings
|
||||
oldInbound.Settings = string(newSettings)
|
||||
|
||||
foundList := make([]string, 0, len(foundEmails))
|
||||
@@ -1181,7 +1183,7 @@ func (s *ClientService) bulkDelInboundClients(
|
||||
// Serialize against the traffic poll to avoid the cross-transaction
|
||||
// lock-order deadlock on inbounds/client_records (runSerializedTx).
|
||||
txErr := runSerializedTx(func(tx *gorm.DB) error {
|
||||
if err := tx.Save(oldInbound).Error; err != nil {
|
||||
if err := commitInboundClientSettings(tx, oldInbound, prevSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
finalClients, err := inboundSvc.GetClients(oldInbound)
|
||||
@@ -1780,7 +1782,7 @@ func (s *ClientService) bulkSetEnableInboundClients(inboundSvc *InboundService,
|
||||
}
|
||||
|
||||
txErr := runSerializedTx(func(tx *gorm.DB) error {
|
||||
if e := tx.Save(oldInbound).Error; e != nil {
|
||||
if e := commitInboundClientSettings(tx, oldInbound, prevSettings); e != nil {
|
||||
return e
|
||||
}
|
||||
finalClients, gcErr := inboundSvc.GetClients(oldInbound)
|
||||
@@ -1850,7 +1852,7 @@ func (s *ClientService) bulkSetEnableInboundClients(inboundSvc *InboundService,
|
||||
}
|
||||
}
|
||||
if !pushFailed {
|
||||
advancePushedInbound(rt, prevSettings, oldInbound)
|
||||
advancePushedInbound(rt, prevSettings, string(newSettings), oldInbound)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user