Integrate reviewed recovery and source visibility fixes
Why: Complete the approved U2/U3 fix pass and prevent shared host fixture collisions. Evidence: Native SELF receipts record complete Backend suite, Frontend 221 tests, meaningful startup regression failures and the bounded baseline model batch. Impact: Pin the corrected Backend and Frontend and allocate owned recovery fixture ports; no existing service deployment. Refs: modules/documents/docs/proposals/2026-10-13-fast-slow-u2-u3-plan.md
This commit is contained in:
File diff suppressed because one or more lines are too long
+1
-1
Submodule modules/backend updated: 3d94c5df1a...4a0418e211
+1
-1
Submodule modules/frontend updated: c63f6c7470...861c45edb5
+22
-14
@@ -184,6 +184,12 @@ esac
|
||||
"""
|
||||
|
||||
|
||||
def free_fixture_port():
|
||||
with socket.socket() as reservation:
|
||||
reservation.bind(("127.0.0.1", 0))
|
||||
return reservation.getsockname()[1]
|
||||
|
||||
|
||||
class SvcAutoRecoverTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
tmp = tempfile.mkdtemp(prefix="svc-autorecover-")
|
||||
@@ -330,14 +336,14 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
# ---- 1. 期望态:start 成功置位 / 显式 stop 清除(含进程已死的 stop)----
|
||||
|
||||
def test_start_records_autostart_and_stop_clears(self):
|
||||
self.seed_agent("dev", 18801)
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
self.assertEqual(self.kv("dev", "autostart"), "1")
|
||||
self.svc("dev", "stop")
|
||||
self.assertIsNone(self.kv("dev", "autostart"))
|
||||
|
||||
def test_stop_on_dead_process_clears_autostart(self):
|
||||
self.seed_agent("dev", 18802)
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
self.kill9("dev")
|
||||
# 进程已死但 autostart 保留(等待 recover);显式 stop 表示"保持停止"。
|
||||
@@ -348,7 +354,8 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
# ---- 2. recover:崩溃拉回 / 运行中不动 / 显式停止不复活 / 未启动不碰 ----
|
||||
|
||||
def test_recover_restarts_sigkilled_service(self):
|
||||
self.seed_agent("dev", 18803)
|
||||
port = free_fixture_port()
|
||||
self.seed_agent("dev", port)
|
||||
self.start_ok("dev")
|
||||
old_pid = self.kill9("dev")
|
||||
result = self.svc("recover", "--quiet")
|
||||
@@ -361,10 +368,10 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
ok_events = self.history_events("dev", "recover")
|
||||
self.assertTrue(any('"result":"ok"' in l for l in ok_events), ok_events)
|
||||
# 恢复用的仍是持久化 exec=(svc.env 登记未漂移)。
|
||||
self.assertIn("--addr 127.0.0.1:18803", self.kv("dev", "exec"))
|
||||
self.assertIn("--addr 127.0.0.1:%d" % port, self.kv("dev", "exec"))
|
||||
|
||||
def test_recover_noop_while_running(self):
|
||||
self.seed_agent("dev", 18804)
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
pid = self.pid_of("dev")
|
||||
result = self.svc("recover")
|
||||
@@ -373,7 +380,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
self.assertEqual(self.history_events("dev", "recover"), [])
|
||||
|
||||
def test_recover_respects_explicit_stop(self):
|
||||
self.seed_agent("dev", 18805)
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
self.svc("dev", "stop")
|
||||
result = self.svc("recover")
|
||||
@@ -382,7 +389,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
self.assertEqual(self.history_events("dev", "recover"), [])
|
||||
|
||||
def test_recover_ignores_never_started_service(self):
|
||||
self.seed_agent("dev", 18806) # 从未 start:无 autostart 键
|
||||
self.seed_agent("dev", free_fixture_port()) # 从未 start:无 autostart 键
|
||||
result = self.svc("recover")
|
||||
self.assertEqual(result.returncode, 0, result.stdout)
|
||||
self.assertFalse(self.pid_alive(self.pid_of("dev")))
|
||||
@@ -391,7 +398,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
# ---- 3. 并发防护:生命周期锁被持有时 recover 跳过 ----
|
||||
|
||||
def test_recover_skips_when_lock_held(self):
|
||||
self.seed_agent("dev", 18807)
|
||||
self.seed_agent("dev", free_fixture_port())
|
||||
self.start_ok("dev")
|
||||
self.kill9("dev")
|
||||
lock = self.runtime("dev") / ".start.lock"
|
||||
@@ -411,11 +418,12 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
# ---- 4. 恢复失败路径:端口被外部进程占用 → fail 事件 + 非零退出 ----
|
||||
|
||||
def test_recover_fails_when_port_stolen(self):
|
||||
self.seed_agent("dev", 18808)
|
||||
port = free_fixture_port()
|
||||
self.seed_agent("dev", port)
|
||||
self.start_ok("dev")
|
||||
self.kill9("dev")
|
||||
thief = subprocess.Popen(
|
||||
["python3", "-m", "http.server", "18808", "--bind", "127.0.0.1"],
|
||||
["python3", "-m", "http.server", str(port), "--bind", "127.0.0.1"],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
self.addCleanup(thief.terminate)
|
||||
# 等 thief 真正完成 bind 再 recover——慢环境里 Popen 返回时端口尚未监听,
|
||||
@@ -426,7 +434,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
s = socket.socket()
|
||||
s.settimeout(0.2)
|
||||
try:
|
||||
s.connect(("127.0.0.1", 18808))
|
||||
s.connect(("127.0.0.1", port))
|
||||
bound = True
|
||||
except OSError:
|
||||
pass
|
||||
@@ -479,7 +487,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
"""模拟 update 误清 pidfile 后本 bin 仍占口:裸 start 应回收并拉起新进程。"""
|
||||
if not self._ss_can_attribute_pid():
|
||||
self.skipTest("ss -ltnp 无法归因 PID(reclaim_own_port 需此能力)")
|
||||
port = 18812
|
||||
port = free_fixture_port()
|
||||
self.seed_agent("dev", port)
|
||||
self.start_ok("dev")
|
||||
old = self.pid_of("dev")
|
||||
@@ -500,7 +508,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
|
||||
def test_start_rejects_foreign_port_occupant(self):
|
||||
"""外部 python http.server 占口:start 必须失败并提示非本服务。"""
|
||||
port = 18813
|
||||
port = free_fixture_port()
|
||||
self.seed_agent("dev", port)
|
||||
thief = subprocess.Popen(
|
||||
["python3", "-m", "http.server", str(port), "--bind", "127.0.0.1"],
|
||||
@@ -537,7 +545,7 @@ class SvcAutoRecoverTests(unittest.TestCase):
|
||||
self.assertFalse((unit_dir / "agent-system-svc-recover.service").exists())
|
||||
|
||||
def test_recover_reserved_name_rejected(self):
|
||||
self.seed_agent("recover", 18809) # 保留字不可作服务名
|
||||
self.seed_agent("recover", free_fixture_port()) # 保留字不可作服务名
|
||||
result = self.svc("recover", "recover", check=False)
|
||||
self.assertEqual(result.returncode, 2, result.stdout)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user