"""沙箱与清理的单一口径(纯函数层,只依赖标准库)。 收敛前分层口径散在各处注释里,本模块把它钉成可单测 的函数,调用点只传事实、不再各写一套: 1. /tmp 隔离矩阵(Docker 外层 vs 宿主 bwrap 内层): - Docker 执行器:容器 /tmp 与 /var/tmp 固定为 tmpfs (sandbox.build_run_command 在额外挂载之后追加 --tmpfs,且 /tmp 目标的额外挂载在解析时即被拒绝), 容器内 /tmp 绝不落在宿主 /tmp。 - 宿主 bwrap / 本地执行器:/tmp 不隔离。宿主级 bwrap 的 /tmp 会落到宿主 /tmp 上,内层 codex 的 bwrap --proc 挂载亦不受本仓库控制——本模块只如实标注, 绝不宣称“内层沙箱已修”。 - 任务容器名正则(codex-<32 位 hex 任务 id>)此前只 活在 sandbox.cleanup_stale_containers 的注释与正则里; 现收为 TASK_CONTAINER_RE,sandbox 直接复用它。 清理绝不按 "codex-" 前缀删:基础设施容器 (如 codex-postgres)名字不匹配正则,永远不被删。 2. 失联清理判定(cleanup_decision):worker _reap_kill_requests 的状态与心跳两道门——任务必须 已是 interrupted,且心跳停滞超过阈值(心跳取 COALESCE(last_heartbeat, updated_at),与 server.sweep_stale_tasks 同口径;无心跳时间戳时按 已停滞处理,与 _reap 旧代码的 fall-through 一致; 恰等于阈值时不杀,旧代码是 hb >= now - threshold 即 跳过)。请求 pending、执行器有 kill_container、容器 名由任务 id 派生这三道门仍在 worker 侧,不进本层: 本层只判“该不该杀”,不执行杀。 3. Worker 寻址(worker_address):workers 注册表只有 worker_id 一列身份,默认形为“主机名-进程号” (worker.py,未给 --worker-id 时)。本函数从该串 派生 (host, pid):取最后一个连字符后全为数字的一段 作 pid,否则 pid 为 None。注册表本身不加 host/pid 列——加列需 PG 迁移与 test_p2_migrations 登记,超出 本轨收敛范围;寻址先用派生函数,不动表结构。 单一状态来源仍是:workers 表(注册与心跳)+ tasks.last_heartbeat(陈旧判定)+ kill_requests (清理意图),三者缺一不可,不得另起一套状态表。 """ import re TMPFS_PATHS = ("/tmp", "/var/tmp") TASK_CONTAINER_RE = re.compile(r"^codex-[0-9a-f]{32}$") def tmpfs_isolated(executor_kind, path): """某执行器下某路径是否走 tmpfs 隔离:(isolated, reason)。""" if executor_kind == "docker" and path in TMPFS_PATHS: return True, "docker --tmpfs 隔离,容器内路径不落宿主" if executor_kind == "docker": return False, "docker 下仅 /tmp 与 /var/tmp 走 tmpfs" if executor_kind in ("host-bwrap", "local"): return False, "宿主 bwrap / 本地执行器 /tmp 不隔离,会落宿主" return False, "未知执行器,按不隔离处理(fail-closed 口径)" def cleanup_decision(status, heartbeat_ts, now, threshold_seconds): """失联清理判定:放行回 (True, reason),否则 (False, reason)。 status 必须为 'interrupted';心跳停滞须严格超过阈值 (恰等于阈值不杀);heartbeat_ts 为 None 时按已停滞 处理(与 _reap 旧代码一致:无时间戳不阻拦收殓)。 """ if status != "interrupted": return False, "任务不在 interrupted,不杀(防复活任务被旧请求误杀)" if heartbeat_ts is None: return True, "无心跳时间戳,按已停滞处理" try: age = now - float(heartbeat_ts) except (TypeError, ValueError): return False, "心跳时间戳不可解析,不杀" if age > threshold_seconds: return True, "心跳停滞 %.0f 秒,超过阈值 %s 秒" % ( age, threshold_seconds) return False, "owner 可能还活着(心跳 %d 秒前),留待下轮" % age def worker_address(worker_id): """从 worker_id 派生 (host, pid):pid 取末段纯数字,否则 None。""" if not worker_id or not isinstance(worker_id, str): return "", None if "-" in worker_id: host, _, tail = worker_id.rpartition("-") if host and tail.isdigit(): return host, int(tail) return worker_id, None