mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-15 17:16:07 +00:00
e4d4ab3a63
ResetAllTraffics and ResetInboundTraffic performed their remote-node reset HTTP calls inside submitTrafficWrite. Each call can block up to the remote timeout, and Reset All Traffics loops every node serially, so the single traffic-writer goroutine was held for seconds — long enough that the concurrent 5s traffic poll timed out submitting its own write and dropped the deltas it had already drained from xray. Do the DB reset inside the writer, then propagate to the nodes after it returns, matching how the mtproto quota reset is already sequenced.
72 lines
2.0 KiB
Go
72 lines
2.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
|
|
)
|
|
|
|
type blockingResetRuntime struct {
|
|
fakeNodeRuntime
|
|
reached chan struct{}
|
|
release chan struct{}
|
|
}
|
|
|
|
func (b *blockingResetRuntime) ResetAllTraffics(context.Context) error {
|
|
close(b.reached)
|
|
<-b.release
|
|
return nil
|
|
}
|
|
|
|
// A "Reset All Traffics" that propagates to nodes must not hold the single serial
|
|
// traffic writer across the remote HTTP calls: each can block for seconds, and
|
|
// stalling the writer drops every concurrent poll's traffic deltas.
|
|
func TestResetAllTrafficsDoesNotBlockWriterOnNodeCall(t *testing.T) {
|
|
db := initTrafficTestDB(t)
|
|
resetTrafficWriterForTest(t)
|
|
StartTrafficWriter()
|
|
|
|
mgr := runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }})
|
|
runtime.SetManager(mgr)
|
|
t.Cleanup(func() { runtime.SetManager(nil) })
|
|
|
|
node := &model.Node{Name: "n1", Address: "127.0.0.1", Port: 2096, ApiToken: "tok", Enable: true, Status: "online"}
|
|
if err := db.Create(node).Error; err != nil {
|
|
t.Fatalf("create node: %v", err)
|
|
}
|
|
fake := &blockingResetRuntime{reached: make(chan struct{}), release: make(chan struct{})}
|
|
mgr.SetRuntimeOverride(node.Id, fake)
|
|
|
|
done := make(chan error, 1)
|
|
go func() { done <- (&InboundService{}).ResetAllTraffics() }()
|
|
|
|
select {
|
|
case <-fake.reached:
|
|
case <-time.After(3 * time.Second):
|
|
close(fake.release)
|
|
t.Fatal("node ResetAllTraffics was never reached")
|
|
}
|
|
|
|
writerFree := make(chan error, 1)
|
|
go func() { writerFree <- submitTrafficWrite(func() error { return nil }) }()
|
|
select {
|
|
case err := <-writerFree:
|
|
if err != nil {
|
|
close(fake.release)
|
|
t.Fatalf("concurrent writer submit failed: %v", err)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
close(fake.release)
|
|
<-done
|
|
t.Fatal("the serial traffic writer was blocked by a node reset HTTP call")
|
|
}
|
|
|
|
close(fake.release)
|
|
if err := <-done; err != nil {
|
|
t.Fatalf("ResetAllTraffics: %v", err)
|
|
}
|
|
}
|