fix(turn-lane): name the holding machine beside the holding pid - #4812
Conversation
A lane holder record carried only a pid, so a second host that shares one runtime root printed a process id it cannot have. The holder record now carries a sanitized local machine name next to the pid, the lane readback projects it, and the lock operator action names holder_host so an operator inspects the right machine instead of a pid that cannot exist locally. The name is a sanitized label, not a path, and the private lock path and lock id still never leave the process. This is the readback half of the cross-host single-execution row; the shared-mount qualification stays open on its owner-provided environment gate. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
精确 head:2437d3e55d774536448c6bb6958d09bb34851501(base 05cd0181b)。无阻断发现,建议合并;合并决定留给 maintainer。
动机
Turn lane 的 holder record 只写了 pid,没有写是哪台机器写的;lane_fence.turn_lane_holder_readback 又把这个 pid 直接投影进拒绝载荷。于是当两台机器共享同一个 runtime root 时,后到的一台会被告知去等一个在它上面根本不可能存在的进程号;lock 超时的 operator_action 更是直接让运维「inspect the recorded holder PID」,却不说去哪个 host 上找。拒绝本身是 typed 的,但不可执行。
改动思路
把「机器」和「pid」放在同一个身份里,由同一个 owner 产出、由现有读取器投影:
file_lock._identity在写 holder 身份时补一个经过既有_safe_label归一化的本机名(是名字,不是路径),_read_holder_record的允许列表同步放行,保证读回来时字段不会丢;lane_fence.TURN_LANE_HOLDER_TEXT_FIELDS投影 host,所以 lane 拒绝的in_flight里带机器名;_operator_action增加holder_host,并把第一步改成「Inspect the recorded holder host, PID and operation on that host.」。
私有的 lock path 与 lock id 仍然不离开进程;既有的 public-safe 断言(runtime 路径绝不出现在 readback 里)继续成立。
具体改动
loopx/file_lock.py(+11/-1):socket 导入、_identity 增加 host、读取允许列表增加 host、_operator_action 增加 holder_host 并修改步骤文案。loopx/control_plane/turn_driver/lane_fence.py(+11/-6):投影字段元组增加 host,并更新 readback 的 docstring 说明为什么 pid 需要机器限定。tests/test_file_lock.py(+14)、tests/test_turn_lane_fence.py(+7/-1):断言 holder record 的 host、incident 的 holder_host 与 holder 一致、lane readback 的 host 与精确 key set。
对主干的风险
- 纯增字段:holder record、lock 超时 payload、lane 拒绝的
in_flight各加一个host;没有删改任何字段,获取/释放/重试语义、remidiation 与 kernel lock 权威性都没动。 - 精确 key set 断言已披露更新:lane readback 的测试原本断言
{agent_id, operation, pid, acquired_at},这次显式改成加host,是这次行为变化的披露面。 - 反证:把这个 head 的两个测试文件放到 base
05cd0181b上跑,3 个用例以KeyError: 'host'失败(两套件各命中),说明断言在测新增身份而不是复述现状。 - 诚实边界:验证里两台「host」其实是同一台机器,跨主机读取本身没有被观测;这次只保证「readback 会说出机器名」,不证明共享挂载上的 flock 语义,也不能把 holder record 当成跨主机互斥。该行的 ownership carrier 决策与真实两主机资格验证仍然挂着。
我的整体评价
正向且比例合适:一行身份字段换掉了「一个不可执行的 pid」,改动落在既有的身份 owner 与既有读取器上,没有引入 lease、TTL、新锁或新配置,也没有把跨主机互斥这种更大的语义偷偷塞进来。归一化复用既有 sanitizer,旧 record 缺字段时按缺失处理而不是编造。没有阻断问题。
English verdict: APPROVE - the holder record, the lock-timeout operator action and the lane refusal now name the holding machine beside the pid (sanitized label, additive fields only), verified by 18 passed in the two owning suites, 44 passed in an adjacent selection, ruff clean, and a base/head counterfactual where 3 assertions fail with KeyError: 'host' on base. It does not qualify cross-host flock semantics or the ownership carrier, which stay open on the row's owner-provided environment gate. Merging is a maintainer decision.
A Turn lane holder record carried a pid and nothing else.
lane_fence.turn_lane_holder_readbackprojected that pid into the refusal payload, so when two hosts share one runtime root the second host is told to wait for a process id that cannot exist on it, and the lock timeout's operator action told an operator to "inspect the recorded holder PID" without saying which machine to look on.The holder record now names its own machine beside the pid:
{"schema_version": "...", "lock_id": "...", "policy": "single_flight", "host": "devbox-01", "pid": 4711, "agent_id": "...", "operation": "loopx_turn_lane", "acquired_at": "..."}file_lock._identityadds a sanitized local machine label (socket.gethostname()through the existing_safe_label, so it is a name, never a path), and_read_holder_recordkeeps it when reading the record back.file_lock._operator_actionnow carriesholder_hostbesideholder_pid, and its first step reads "Inspect the recorded holder host, PID and operation on that host."lane_fence.TURN_LANE_HOLDER_TEXT_FIELDSprojects the host, so a lane refusal'sin_flightentry says which machine holds the lane.The private lock path and lock id still never leave the process; the machine name is a sanitized label and the existing public-safe assertion (
the runtime path never appears in the readback) still holds.This is the readback half of the cross-host single-execution row. The ownership-carrier decision and the two-host shared-mount qualification (exclusive-create atomicity, a two-host refusal, a two-host takeover, and whether
flockis usable on that mount) stay open on their owner-provided environment gate.Validation:
uv run --extra test python -m pytest tests/test_file_lock.py tests/test_turn_lane_fence.py -q-> 18 passed.uv run --extra test python -m pytest tests/test_file_lock.py tests/test_turn_lane_fence.py tests/test_loopx_turn_journal_inspection.py tests/test_turn_route_action_classification.py -q-> 44 passed.uv run --extra test ruff checkon all four changed files -> all checks passed.05cd0181bfail withKeyError: 'host'in both suites plus the holder key-set assertion, so the new assertions detect the missing machine name rather than restating current output.Disclosed behaviour change: the lock holder record and the lock timeout payload gain one additive field, and the lane refusal's
in_flightentry gainshost. No field is removed or renamed.Control-plane runtime change (
loopx/**): proposed for review and left for the maintainer to merge.