Skip to content

Runtime Host PID-known retirement can stop a successor and report the old epoch as retired #3594

Closed

Description

@Astro-Han

Follow-up to #3557, which is already merged (7f68af7655e5bbc74e70f531dea1acfca2e93dc9). This was recorded as a non-blocking [P3] during review and deliberately not treated as a merge hold; opening it so it does not get lost.

The gap

In the PID-known retirement path, packages/cli/src/runtime-host-service-manager.ts:380-417, the old Host is prepared and then backend.stop() is called without holding a State Root fence. Only the service.state === 'starting' branch assigns a rootFence.

If the old process exits abnormally in that window and systemd starts a successor, backend.stop() can stop the successor while the command then reports retirement using the old epoch and PID. The caller is told the host it named was retired; what actually happened is that a different, newer host was stopped.

The post-stop checks do not close this. verifyRuntimeHostRootReleased(root) and the stopped.active/state/pid inspection prove that no writer holds the root at that moment — they cannot prove an identity-continuous handoff, because the identity being asked about may already have been replaced.

Why the obvious fix is not enough

Re-reading identity after stop is too late: by then the successor has already been stopped and any writes it made in the window are unrecoverable. The correct shape is to hold the fence across the stop, or to model retirement as an explicit transition that fails when the observed identity is not the one that was prepared.

Acceptance criteria

  • A crash → restart interleaving test: prepare against a known PID, kill that process abnormally inside the window, let a successor come up, and assert the command does not stop the successor and does not report retirement of the old epoch/PID as success.
  • The State Root fence is held across backend.stop() on the PID-known path, not only on the starting path — or retirement is expressed as an explicit transition that rejects an identity mismatch.
  • The post-stop root-vacancy check is documented for what it actually proves (no current writer) rather than as evidence of a continuous handoff.

Severity

P3 as rated: the window is narrow, there is no concurrent writer, and no direct data corruption was demonstrated. It is a correctness gap in what the command claims happened, which is why it is worth fixing rather than shipping as understood behaviour.

Provenance

Found during #3557 review, with the fix direction corrected in a review addendum (5002040661) after the first proposed fix — a post-stop identity re-read — was shown to be too late. A second reviewer independently reproduced the same window afterwards and labelled it corroboration rather than an independent finding.

Activity

  1. Sun-GLiang commented on Aug 23, 2026

    @Sun-GLiang
    Member

    Hi, I'd like to work on this issue.
    My plan is to hold the State Root retirement fence across backend.stop() on the PID-known path, add a deterministic crash → restart interleaving regression test that verifies the successor is neither stopped nor misreported as the retired Host, and clarify what the post-stop root-vacancy check proves.
    Could you please assign this issue to me?

  2. CuSO41108 commented on Aug 23, 2026

    @CuSO41108
    Contributor

    Hi, I'd like to work on this issue.嗨,我希望能参与这个项目的开发工作。 My plan is to hold the State Root retirement fence across backend.stop() on the PID-known path, add a deterministic crash → restart interleaving regression test that verifies the successor is neither stopped nor misreported as the retired Host, and clarify what the post-stop root-vacancy check proves.我的计划是在 PID 已知路径上的 backend.stop() 处设置状态根节点退役测试点,然后添加一个确定性的崩溃测试——重新执行验证后续节点的测试,确保后续节点既没有被停止,也不会被错误地报告为已退役的节点。同时,还需要明确在节点退役后进行的根节点空缺检查究竟能证明什么。 Could you please assign this issue to me?请能否将这个问题交给我处理?

    Hi @Sun-GLiang, I saw your comment and noticed that our approaches overlap. I have independently implemented the Runtime Host retirement fence and added deterministic regression tests for the successor race.

    I don't want to duplicate your work. Would you prefer to coordinate on one implementation, or should I open my branch as a PR for maintainer review?

  3. CuSO41108 commented on Aug 23, 2026

    @CuSO41108
    Contributor

    Hi @Sun-GLiang @Astro-Han,

    I noticed that #3594 is assigned to @Sun-GLiang. I opened #3602 before
    realizing that the issue had already been assigned.

    Since #3602 already contains the implementation and regression tests, would
    you be open to collaborating on this fix? I’m happy to coordinate with
    @Sun-GLiang on the remaining validation and review, if that works for everyone.

    I don’t want to duplicate anyone’s work, so please let me know how you would
    prefer to proceed.

  4. Sun-GLiang commented on Aug 23, 2026

    @Sun-GLiang
    Member

    Hi @Astro-Han, thank you for assigning #3594 to me.
    I noticed that @CuSO41108 opened #3602 while I was working on the issue independently. I’m happy to collaborate with them, and I’m currently reviewing #3602 against the issue’s acceptance criteria and the relevant retirement lifecycle edge cases.
    I have also completed a local implementation with deterministic regression coverage for the successor race, asynchronous Host drain, stable-stopped transition, and fence cleanup failures. I can share the relevant findings, tests, or focused patches with @CuSO41108 so that we can converge on a single implementation.
    To avoid competing PRs, I won’t open another PR while this coordination is ongoing. Please let us know if you would prefer a different workflow.

  5. Sun-GLiang commented on Aug 23, 2026

    @Sun-GLiang
    Member

    Hi @CuSO41108, thanks for clarifying. I agree that this was parallel work, and I appreciate your willingness to coordinate.
    I’m not a maintainer and I don’t have write access to either your fork branch or the upstream repository, so I’m not in a position to decide which implementation should be merged.
    I had already started working after asking to claim #3594, and I have now completed an independent implementation and local verification. I also reviewed the current head of #3602 as a peer contributor. The core fencing direction is aligned, although I found a few lifecycle, scope, and test-coverage differences that I think are worth discussing.
    To avoid creating competing PRs, I’ll leave the technical findings as comments on #3602 and can share my branch and the relevant tests or patches for you to incorporate. I won’t open another PR unless the maintainers ask for one.
    @Astro-Han, could you please advise whether you would prefer us to continue by improving #3602 together, or whether you would like to compare the alternative implementation before choosing a path?

  6. Astro-Han commented on Aug 23, 2026

    @Astro-Han
    ContributorAuthor

    OK, it seems like two of us has overlapped on this issue. Glad we could work together on this issue, maybe we could focus on solving #3602 and @Sun-GLiang could help @CuSO41108 with code reviews? Thanks to both of you~

  7. CuSO41108 commented on Aug 23, 2026

    @CuSO41108
    Contributor

    Hi @Astro-Han and @Sun-GLiang,

    Thanks for coordinating this. I really appreciate the guidance and Sun’s
    willingness to review the work.

    I’ll continue the implementation and discussion in #3602 for now. @Sun-GLiang,
    please feel free to share the lifecycle, scope, and test-coverage findings,
    along with any focused tests or patches. I’ll review them against the issue’s
    acceptance criteria, and we can discuss any necessary updates there.

    Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinghelp wantedExtra attention is needed

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions