Wait for the port thief to bind before recovering
ci / test-pg (pull_request) Successful in 1m0s
ci / test (pull_request) Failing after 1m3s

Why: Popen returns before python http.server finishes binding, so on
slower CI runners recover won the race and started the service, turning
the port-stolen case into a flake.

Evidence: single-case rerun OK locally; make test-governance rerun
recorded on the task card; CI on the new head is authoritative.

Impact: test synchronization only.

Refs: .task/feature-svc-autorecover.md
This commit is contained in:
2026-10-11 20:49:50 +00:00
parent cd43a62a87
commit b12e94dcd9
2 changed files with 58 additions and 2 deletions
File diff suppressed because one or more lines are too long
+20 -2
View File
@@ -8,8 +8,10 @@
import fcntl
import os
import shutil
import socket
import subprocess
import tempfile
import time
import unittest
from pathlib import Path
@@ -227,10 +229,8 @@ class SvcAutoRecoverTests(unittest.TestCase):
for _ in range(100): # 等 bash 主进程消失(HTTP 子进程由 ppid 看护自杀)
if not self.pid_alive(pid):
break
import time
time.sleep(0.05)
self.assertFalse(self.pid_alive(pid))
import time
time.sleep(0.6)
return pid
@@ -335,6 +335,24 @@ class SvcAutoRecoverTests(unittest.TestCase):
["python3", "-m", "http.server", "18808", "--bind", "127.0.0.1"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
self.addCleanup(thief.terminate)
# 等 thief 真正完成 bind 再 recover——慢环境里 Popen 返回时端口尚未监听,
# recover 会抢先起服务,把“端口被抢”测成竞态(CI 首轮实测)。
deadline = time.monotonic() + 5.0
bound = False
while time.monotonic() < deadline:
s = socket.socket()
s.settimeout(0.2)
try:
s.connect(("127.0.0.1", 18808))
bound = True
except OSError:
pass
finally:
s.close()
if bound:
break
time.sleep(0.05)
self.assertTrue(bound, "thief 未能在 5s 内监听 18808")
try:
result = self.svc("recover", check=False)
self.assertEqual(result.returncode, 1, result.stdout)