fix(sync): mark node dirty inside the mutation transaction (atomic ConfigDirty) (#5611)

* fix(sync): mark node dirty inside the mutation transaction

ConfigDirty is currently set by MarkNodeDirty AFTER the mutation, on a
separate DB handle outside the mutation's transaction. A crash or error
between the committed change and the mark leaves a committed config
change that never reconciles to the node (silent drift). Add
MarkNodeDirtyTx(tx, id) and call it inside each mutation's transaction so
the dirty mark commits atomically with the change.

* fix(test): initialize DB in TestResolveInboundAddress and group gorm import

Two CI failures on this branch:

- race (-shuffle=on): TestResolveInboundAddress reaches resolveInboundAddress -> configuredPublicHost -> GetSubDomain, which reads the global DB. The test never initialized one, relying on another sub-package test to do so first; under shuffle it ran first and nil-dereferenced gorm. Call initSubDB(t) so it is self-sufficient (empty DB yields an empty subDomain, so the subscriber-host fallback still holds).

- golangci goimports: gorm.io/gorm was grouped with the github.com/mhsanaei/3x-ui local imports in node_dirty_test.go. Move it into the third-party group.
This commit is contained in:
n0ctal
2026-06-28 18:18:28 +05:00
committed by GitHub
parent 2b10808fbd
commit aef35ee0de
7 changed files with 171 additions and 149 deletions
+14 -5
View File
@@ -453,12 +453,14 @@ func (s *NodeService) Update(id int, in *model.Node) error {
"inbound_tags": string(inboundTagsJSON),
"outbound_tag": in.OutboundTag,
}
if err := db.Model(model.Node{}).Where("id = ?", id).Updates(updates).Error; err != nil {
if err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(model.Node{}).Where("id = ?", id).Updates(updates).Error; err != nil {
return err
}
return s.MarkNodeDirtyTx(tx, id)
}); err != nil {
return err
}
if dErr := s.MarkNodeDirty(id); dErr != nil {
logger.Warning("mark node dirty after update failed:", dErr)
}
if mgr := runtime.GetManager(); mgr != nil {
mgr.InvalidateNode(id)
}
@@ -736,10 +738,17 @@ func (s *NodeService) warnOnDuplicateGuid(id int, guid string) {
}
func (s *NodeService) MarkNodeDirty(id int) error {
return s.MarkNodeDirtyTx(database.GetDB(), id)
}
func (s *NodeService) MarkNodeDirtyTx(tx *gorm.DB, id int) error {
if id <= 0 {
return nil
}
return database.GetDB().Model(model.Node{}).
if tx == nil {
return errors.New("nil db transaction")
}
return tx.Model(model.Node{}).
Where("id = ?", id).
Updates(map[string]any{
"config_dirty": true,