fix(xray): do not revive a manually stopped Xray on a background restart

RestartXray cleared isManuallyStopped unconditionally at its top, so the @30s
pending-config cron (and warp/ldap/outbound reconcile jobs) that call
RestartXray(false) resurrected an Xray the admin had deliberately stopped —
unlike the crash-detector, which honors the manual-stop flag. Skip a non-forced
restart while the stop flag is set; only an explicit forced restart clears it.
This commit is contained in:
MHSanaei
2026-07-15 03:24:43 +02:00
parent 89c27c5835
commit 116ef900d5
2 changed files with 29 additions and 0 deletions
+6
View File
@@ -986,6 +986,12 @@ func (s *XrayService) RestartXray(isForce bool) error {
lock.Lock()
defer lock.Unlock()
logger.Debug("restart Xray, force:", isForce)
// A background reconcile (a pending config-change flag, warp/ldap/outbound
// jobs) must not revive an Xray the admin deliberately stopped; only an
// explicit forced restart clears the manual-stop state.
if !isForce && isManuallyStopped.Load() {
return nil
}
isManuallyStopped.Store(false)
xrayConfig, err := s.GetXrayConfig()
+23
View File
@@ -0,0 +1,23 @@
package service
import (
"testing"
)
// A background (non-forced) restart — the pending-config-change cron, warp/ldap/
// outbound reconcile jobs — must not revive an Xray the admin deliberately
// stopped. Only an explicit forced restart clears the manual-stop state.
func TestRestartXrayRespectsManualStop(t *testing.T) {
setupSettingTestDB(t)
if err := (&SettingService{}).saveSetting("xrayTemplateConfig", "{ not valid json"); err != nil {
t.Fatalf("seed template: %v", err)
}
t.Cleanup(func() { isManuallyStopped.Store(false) })
isManuallyStopped.Store(true)
_ = (&XrayService{}).RestartXray(false)
if !isManuallyStopped.Load() {
t.Fatal("a non-forced restart cleared a deliberate manual stop and would revive xray")
}
}