part 4 缺陷 1:cell 启动竞态——起步门 + init pid 轮询

RETEST3 缺陷 1:run_cell 里配网与 payload 零同步,快 payload
(run -- true)在配网完成前退出即撞墙,11 次只成 6 次。
本提交先落 helper(接线在缺陷 3 的 run_cell 重写里做):
- gate_wrapper_command():payload 包一层 sh 起步门,等
  /run/chv/.cell-gate-<machine> 出现再 exec(绝对路径
  /usr/bin/sh:volatile 单元根里 /bin 未必有)。
- find_cell_init_pid() 改带超时轮询(timeout 默认 0.0 = 单次,
  旧调用行为不变;缺目录不再直接回 None;proc_alive 回调让
  nspawn 已死时立刻回,不空等)。
新测试:CellGateTest、CellInitPidPollTest(5 个)。
This commit is contained in:
host committed 2026-11-07 10:29:13 +00:00
1 parent 179a86d655
commit fc33eed262
2 files changed
+120 -15

No files matched your search

+50 -15
View File
@@ -235,24 +235,45 @@ def host_network_cleanup_commands(uplink_iface):
return cmds
def find_cell_init_pid(nspawn_pid, proc_root="/proc"):
def find_cell_init_pid(nspawn_pid, proc_root="/proc", timeout=0.0,
interval=0.1, proc_alive=None):
"""找单元 init 的 pid:nspawn 进程的第一个子进程
(--as-pid2 时它是 stub init,payload 是 PID 2;两者同 netns)。
timeout=0(默认)即只查一次(旧行为);timeout>0 时带超时轮询
(part 4 竞态修复:init 可能还没 fork,单次查找即误判)。
proc_alive 供调用方传“nspawn 是否还活着”(如 proc.poll() is
None 的 lambda):nspawn 已退出即立刻回 None,不再空等超时。
找不到返回 None。"""
try:
task_dir = os.path.join(proc_root, str(nspawn_pid), "task")
for tid in os.listdir(task_dir):
children_path = os.path.join(task_dir, tid, "children")
try:
with open(children_path) as fh:
children = fh.read().split()
except OSError:
continue
if children:
return int(children[0])
except (OSError, ValueError):
return None
return None
deadline = time.time() + timeout
while True:
found = None
try:
task_dir = os.path.join(proc_root, str(nspawn_pid), "task")
for tid in os.listdir(task_dir):
children_path = os.path.join(task_dir, tid, "children")
try:
with open(children_path) as fh:
children = fh.read().split()
except OSError:
continue
if children:
try:
found = int(children[0])
except ValueError:
continue
break
except OSError:
# 任务目录还没出现(init 未 fork)即当作“这次没找到”,
# 继续轮询,不直接回 None(旧代码这里直接 return None,
# 正是单次查找误判的根因之一)。
found = None
if found is not None:
return found
if proc_alive is not None and not proc_alive():
return None
if time.time() >= deadline:
return None
time.sleep(min(interval, max(0.0, deadline - time.time())))
def cell_network_commands(cell_pid):
@@ -282,6 +303,20 @@ def _ensure_bind_source(path, uid=CELL_UID, gid=CELL_GID):
raise
def gate_wrapper_command(command, gate_path):
"""把单元内命令包一层起步门(纯函数):payload 先在单元内
等 gate 文件出现再 exec 原命令。gate 文件由 run_cell 在配网
完成后才创建(见 run_cell)——payload 与配网零同步是 RETEST3
新缺陷 1 的根因(快 payload 在配网完成前退出,nspawn 随之
退出,包装器再去配网即撞墙)。/usr/bin/sh 用绝对路径:
volatile 单元根里 /bin 未必有,/usr 是只读挂进来的,必有。
注意:$1 是 gate 路径,shift 后 "$@" 即原命令。"""
script = ("while [ ! -e \"$1\" ]; do sleep 0.05; done; "
"shift; exec \"$@\"")
return ["/usr/bin/sh", "-c", script, "chv-cell-gate",
gate_path] + list(command)
def _wait_for_iface(iface, timeout=10.0):
deadline = time.time() + timeout
while time.time() < deadline: