Files
3x-ui/internal/web/service/inbound_mtproto_txfail_test.go
T
MHSanaei 781458d878 fix(inbound): defer a local MTProto inbound edit's sidecar push until after commit
UpdateInbound applied a local MTProto inbound change by calling the runtime
UpdateInbound (which stops/starts the mtg sidecar or talks to it) from inside
runSerializedTx. That runs process and network I/O on the single traffic-writer
goroutine while a DB transaction is open, so a slow sidecar stalls traffic
accounting and every concurrent client mutation, and a later step failing the
transaction leaves the sidecar ahead of the rolled-back row. Move the push into
the post-commit hook, matching the xray branch. Adds a SetLocalRuntimeOverride
test seam mirroring the existing node override so the deferral is regression
tested.
2026-07-15 00:06:53 +02:00

54 lines
1.9 KiB
Go

package service
import (
"errors"
"testing"
"gorm.io/gorm"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
)
// A local MTProto inbound edit must not push to the managed sidecar from inside
// the serialized write transaction: that blocks the single traffic-writer
// goroutine on process/network I/O, and a later step failing the transaction
// would leave the sidecar ahead of the rolled-back database. The push belongs in
// the post-commit hook, exactly as the xray branch already does it.
func TestUpdateInboundLocalMtprotoDefersPushUntilCommit(t *testing.T) {
setupConflictDB(t)
mgr := runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }})
fake := &fakeNodeRuntime{}
mgr.SetLocalRuntimeOverride(fake)
runtime.SetManager(mgr)
t.Cleanup(func() { runtime.SetManager(nil) })
seedInboundConflict(t, "mt-txfail", "", 46150, model.MTProto, "",
`{"clients":[{"email":"mtx","secret":"`+mtprotoTestSecretA+`","enable":true}]}`)
seeded := loadInboundByTag(t, "mt-txfail")
seedClientTraffic(t, seeded.Id, "mtx", true)
db := database.GetDB()
const cbName = "b1-05:fail-inbound-update"
if err := db.Callback().Update().After("gorm:update").Register(cbName, func(tx *gorm.DB) {
if tx.Statement != nil && tx.Statement.Table == "inbounds" {
tx.AddError(errors.New("injected transaction failure"))
}
}); err != nil {
t.Fatalf("register callback: %v", err)
}
t.Cleanup(func() { _ = db.Callback().Update().Remove(cbName) })
update := *loadInboundByTag(t, "mt-txfail")
update.Remark = "edited"
if _, _, err := (&InboundService{}).UpdateInbound(&update); err == nil {
t.Fatal("UpdateInbound: expected the injected transaction failure")
}
if n := fake.updateInbound.Load(); n != 0 {
t.Fatalf("the MTProto sidecar push ran %d time(s) inside the failed transaction; it must be deferred until the commit succeeds", n)
}
}