Skip to content

fix(turn-lane): name the holding machine beside the holding pid - #4812

Merged
huangruiteng merged 1 commit into
mainfrom
codex/turn-lane-holder-host-20260920
Sep 20, 2026
Merged

huangruiteng merged 1 commit into
mainfrom
codex/turn-lane-holder-host-20260920

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

A Turn lane holder record carried a pid and nothing else. lane_fence.turn_lane_holder_readback projected 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._identity adds a sanitized local machine label (socket.gethostname() through the existing _safe_label, so it is a name, never a path), and _read_holder_record keeps it when reading the record back.
  • file_lock._operator_action now carries holder_host beside holder_pid, and its first step reads "Inspect the recorded holder host, PID and operation on that host."
  • lane_fence.TURN_LANE_HOLDER_TEXT_FIELDS projects the host, so a lane refusal's in_flight entry 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 flock is 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 check on all four changed files -> all checks passed.
  • Base/head counterfactual: the same two test files on base 05cd0181b fail with KeyError: '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_flight entry gains host. No field is removed or renamed.

Control-plane runtime change (loopx/**): proposed for review and left for the maintainer to merge.

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 huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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。

对主干的风险

  1. 纯增字段:holder record、lock 超时 payload、lane 拒绝的 in_flight 各加一个 host;没有删改任何字段,获取/释放/重试语义、remidiation 与 kernel lock 权威性都没动。
  2. 精确 key set 断言已披露更新:lane readback 的测试原本断言 {agent_id, operation, pid, acquired_at},这次显式改成加 host,是这次行为变化的披露面。
  3. 反证:把这个 head 的两个测试文件放到 base 05cd0181b 上跑,3 个用例以 KeyError: 'host' 失败(两套件各命中),说明断言在测新增身份而不是复述现状。
  4. 诚实边界:验证里两台「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.

@huangruiteng
huangruiteng merged commit 2caada6 into main Sep 20, 2026
28 checks passed
@huangruiteng
huangruiteng deleted the codex/turn-lane-holder-host-20260920 branch September 20, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant