mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-16 01:26:07 +00:00
fix(db): clamp traffic counters below int64 max and repair overflowed rows
A counter pushed past int64 (multi-node setups hit this via historic delta-compounding bugs) makes SQLite silently promote the INTEGER cell to REAL. From then on the column no longer scans into the Go int64 field and every reader of client_traffics fails at once: the inbounds page, xray restarts, and node traffic sync all return "converting driver.Value type float64 to int64". Two-part fix: every unbounded "up = up + ?" add (local traffic, node delta merge, inbound counters, plus the Go-side outbound accumulation) now saturates at TrafficMax, a cap safely below math.MaxInt64 so one more delta cannot overflow; and a startup repair casts REAL-promoted cells back to INTEGER and clamps all traffic counters into [0, TrafficMax] across client_traffics, inbounds, outbound_traffics and node_client_traffics, restoring access to already-corrupted panels without manual sqlite surgery. Closes #5762
This commit is contained in:
@@ -113,6 +113,9 @@ func initModels() error {
|
||||
if err := normalizeInboundSubSortIndex(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := repairOverflowedTrafficCounters(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateLegacySocksInboundsToMixed(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -518,6 +521,51 @@ func normalizeInboundSubSortIndex() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// repairOverflowedTrafficCounters heals traffic counters that historic
|
||||
// compounding bugs pushed past int64: on SQLite an overflowing INTEGER is
|
||||
// silently promoted to REAL, after which the column no longer scans into the
|
||||
// Go int64 field and every reader of the table fails (#5762). REAL cells are
|
||||
// cast back to INTEGER (SQLite caps the cast at math.MaxInt64), then values
|
||||
// are clamped into [0, TrafficMax] on both backends so the next delta cannot
|
||||
// overflow again.
|
||||
func repairOverflowedTrafficCounters() error {
|
||||
targets := []struct {
|
||||
table string
|
||||
columns []string
|
||||
}{
|
||||
{"client_traffics", []string{"up", "down"}},
|
||||
{"inbounds", []string{"up", "down"}},
|
||||
{"outbound_traffics", []string{"up", "down", "total"}},
|
||||
{"node_client_traffics", []string{"up", "down"}},
|
||||
}
|
||||
for _, target := range targets {
|
||||
for _, col := range target.columns {
|
||||
statements := []string{
|
||||
fmt.Sprintf("UPDATE %s SET %s = %d WHERE %s > %d", target.table, col, TrafficMax, col, TrafficMax),
|
||||
fmt.Sprintf("UPDATE %s SET %s = 0 WHERE %s < 0", target.table, col, col),
|
||||
}
|
||||
if !IsPostgres() {
|
||||
statements = append([]string{
|
||||
fmt.Sprintf("UPDATE %s SET %s = CAST(%s AS INTEGER) WHERE typeof(%s) = 'real'", target.table, col, col, col),
|
||||
}, statements...)
|
||||
}
|
||||
var repaired int64
|
||||
for _, statement := range statements {
|
||||
res := db.Exec(statement)
|
||||
if res.Error != nil {
|
||||
log.Printf("Error repairing %s.%s: %v", target.table, col, res.Error)
|
||||
return res.Error
|
||||
}
|
||||
repaired += res.RowsAffected
|
||||
}
|
||||
if repaired > 0 {
|
||||
log.Printf("Repaired %d overflowed %s.%s value(s)", repaired, target.table, col)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isIgnorableDuplicateColumnErr(err error, mdl any) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user