"""超时默认值的单一配置源。 背景:主线(origin/main)上同一个“3600 秒”以字面量散落在 多处——审批队列(cloud/approvals.py)、协调员 (cloud/coordinator.py)、接线层任务超时 (cloud/harness2_tasks.py)、标记轮询上限 (cloud/marker_tools.py)、服务端审批入口(cloud/server.py), 另有单轮护栏 600 秒(host.py 的 turn_timeout 默认值,见下) 与陈旧回收 300 秒(server.sweep_stale_tasks / worker._reap_kill_requests)。改一处漏一处是真实风险: 本次收敛前,审批入口与队列模块的上下界就各写了一套。 本模块只依赖标准库,是叶子模块:任何 cloud 模块都可 import 它而不引入循环。每个用途各有名常量,值即使相同 也不共用一个名字——改审批默认绝不能顺带改掉轮询上限。 各常量与现状(改值前必须先读本段): - APPROVAL_DEFAULT_SECONDS = 3600:新审批项的默认等待 秒数。覆盖层:环境变量 CLOUD_APPROVAL_TIMEOUT_SECONDS, 仅当解析为 60..86400 的整数时生效,否则回落默认值。 - APPROVAL_MIN_SECONDS / APPROVAL_MAX_SECONDS = 60 / 86400: 服务端审批入口的校验区间(保持与旧行为一致)。 - LONG_TIMEOUT_SECONDS = 3600:协调员、接线层任务超时、 标记轮询上限三处共用的数值,各模块仍保留自己的常量名 作为别名(调用点与测试 import 的名字不变)。 - TURN_SECONDS = 600:host.py 单轮护栏的默认值。host.py 不在 cloud/ 内,本轨文件边界不改它;这里只登记数值与 含义,杜绝“审批 600 秒”的误读——主线上不存在 600 秒的 审批超时,600 秒是单轮护栏,不是审批等待。 - STALE_SWEEP_SECONDS = 300:陈旧回收与失联收殓共用的 阈值默认值(server 与 worker 各保留自己的参数名)。 - WORKER_ALIVE_SECONDS = 120:worker 注册表存活阈值。 已建审批项的迁移语义(冻结,不可悄悄改): - 本模块没有任何回填、改写 expires_at 的函数。默认值或 环境变量变化只影响之后新建的审批项;已建项的 expires_at 在创建时已落库,之后一律不动。过期扫荡仍按行上的 expires_at 判定,绝不按当前默认值重算。 - 服务端入口的校验与队列模块的创建语义各有分工,不可 混用:服务端入口只接受 60..86400 的整数(见 validate_server_timeout,显式 null、布尔值、浮点数、 越界一律拒绝,与旧行为一致);队列模块级创建允许 timeout_seconds=None(用默认值)与 0(不过期,见 resolve_module_timeout),服务端入口永远传不出 0。 """ import os APPROVAL_DEFAULT_SECONDS = 3600 APPROVAL_MIN_SECONDS = 60 APPROVAL_MAX_SECONDS = 86400 LONG_TIMEOUT_SECONDS = 3600 TURN_SECONDS = 600 STALE_SWEEP_SECONDS = 300 WORKER_ALIVE_SECONDS = 120 def approval_default_seconds(override=None): """新审批项的默认等待秒数:显式参数 > 环境变量 > 默认值。 环境变量 CLOUD_APPROVAL_TIMEOUT_SECONDS 只有解析为 60..86400 的整数时才生效;非法、越界、布尔语义一律 回落默认值,绝不抛异常(默认值解析失败不能炸掉建项)。 """ if override is not None: try: value = int(override) except (TypeError, ValueError): return APPROVAL_DEFAULT_SECONDS if isinstance(override, bool): return APPROVAL_DEFAULT_SECONDS if APPROVAL_MIN_SECONDS <= value <= APPROVAL_MAX_SECONDS: return value return APPROVAL_DEFAULT_SECONDS raw = os.environ.get("CLOUD_APPROVAL_TIMEOUT_SECONDS") or "" try: value = int(raw) except (TypeError, ValueError): return APPROVAL_DEFAULT_SECONDS if APPROVAL_MIN_SECONDS <= value <= APPROVAL_MAX_SECONDS: return value return APPROVAL_DEFAULT_SECONDS def validate_server_timeout(value): """服务端审批入口的超时校验:返回合法整数,否则 ValueError。 与旧入口行为逐字一致:缺省由调用方先填默认值;显式 null、布尔值、浮点数、非整数、越界一律拒绝,错误文案 保持 "timeout_seconds must be 60..86400" 不变。 """ if isinstance(value, bool) or not isinstance(value, int): raise ValueError("timeout_seconds must be 60..86400") if value < APPROVAL_MIN_SECONDS or value > APPROVAL_MAX_SECONDS: raise ValueError("timeout_seconds must be 60..86400") return value def resolve_module_timeout(value, default=None): """队列模块级创建的超时解析:返回秒数或 None(不过期)。 None 用默认值;0 表示不过期(只允许模块级调用,服务 端入口另有限制,永远传不出 0);负数、布尔值、非数字 一律 ValueError。浮点数沿用旧行为:接受并按原值参与 expires_at 计算(服务端入口不走这条路,不受影响)。 """ if default is None: default = approval_default_seconds() if value is None: return default if isinstance(value, bool) or not isinstance(value, (int, float)): raise ValueError("timeout_seconds invalid") if value < 0: raise ValueError("timeout_seconds invalid") if not value: return None return value def expires_at_for(now, timeout_seconds): """由创建时刻与解析后的超时算 expires_at:不过期回 None。""" if not timeout_seconds: return None return now + timeout_seconds