Skip to content

fix(wake-lib): derive Linux pid identity from /proc ticks, not ps lstart#392

Open
iiiwulatam-blip wants to merge 3 commits into
kunchenguid:mainfrom
iiiwulatam-blip:fm/fmwatch-identity-clockstep
Open

fix(wake-lib): derive Linux pid identity from /proc ticks, not ps lstart#392
iiiwulatam-blip wants to merge 3 commits into
kunchenguid:mainfrom
iiiwulatam-blip:fm/fmwatch-identity-clockstep

Conversation

@iiiwulatam-blip

Copy link
Copy Markdown

Summary

fm_pid_identity in bin/fm-wake-lib.sh used ps -o lstart= to build the opaque identity string that fm_watcher_lock_matches_pid compares against a recorded watcher/daemon lock. ps -o lstart= renders from /proc/<pid>/stat's starttime combined with the kernel's boot time (/proc/stat's btime), and btime shifts whenever the wall clock is stepped (settimeofday). On WSL2 the guest clock steps repeatedly after boot, so a watcher's recorded identity drifted out of sync with a fresh read of the same still-running process, and fm_watcher_lock_matches_pid wrongly rejected a healthy, still-running watcher as dead.

On Linux, fm_pid_identity now reads /proc/<pid>/stat field 22 (starttime, in clock ticks since boot) combined with the process command line from /proc/<pid>/cmdline - both immune to wall-clock steps. Non-Linux platforms (Darwin) keep the previous ps -o lstart= -o command= identity unchanged as a fallback, since /proc is Linux-only.

The identity is treated as an opaque string throughout the codebase, so this is a compatible format change: a lock written under the old ps lstart-based format reads as unhealthy exactly once (the first health check after upgrade) and self-heals on the next arm/restart, the same recovery path already used for any other stale-lock case.

Four tests were added to tests/fm-watcher-lock.test.sh:

  • identity stability across consecutive reads of the same process
  • identity distinctness across different processes
  • fm_watcher_lock_matches_pid accept/reject behavior
  • confirmation that the Linux identity is ticks-derived, not a rendered wall-clock date

The actual clock-step regression cannot be reproduced in a unit test (the system clock can't be stepped in-test); these are the honest unit-level proxies for it.

This PR's head also includes two commits produced by this repo's own no-mistakes validation pipeline during the run described below: a test-fixture stabilization commit (unrelated flaky/environment-sensitive assertions in tests/fm-bootstrap.test.sh and tests/fm-dispatch-select.test.sh) and a documentation-sync commit (docs/architecture.md, docs/scripts.md, docs/turnend-guard.md, .agents/skills/afk/SKILL.md) describing the new Linux PID-identity behavior. Both are included because the pipeline run below validated this exact head.

Evidence: no-mistakes pipeline run

This branch was validated end-to-end through this repo's own no-mistakes gate (rebase, review, test, document, lint), with 0 findings at every step, run id 01KX4GV98W8TMP50HJ1HJKJ91G, resulting head 4a0fa47d. Only the pipeline's own push step failed, with a plain permissions error against the upstream origin remote (the authenticated account backing this task has no write access there and no pre-existing fork). That push failure is a credentials/target issue, not a finding about the code, so this run stands as the validation evidence for the head pushed here; this PR was opened manually (push to a fork, then this PR) as the documented fallback.

Test plan

  • shellcheck bin/fm-wake-lib.sh - clean
  • bash tests/fm-watcher-lock.test.sh - all tests pass (27/27, including the 4 new ones)
  • bash tests/fm-turnend-guard.test.sh - all tests pass, no regressions (format-agnostic consumer of fm_pid_identity)
  • bash tests/fm-daemon.test.sh - all tests pass, no regressions (format-agnostic consumer)
  • Full no-mistakes pipeline (rebase/review/test/document/lint) - 0 findings, see evidence section above

ps -o lstart= renders from /proc/stat's btime plus starttime ticks, and
btime shifts whenever the wall clock is stepped (settimeofday). On
WSL2 the guest clock steps repeatedly after boot, so a watcher's
recorded pid identity drifted out of sync with a fresh read for the
same still-running process, and fm_watcher_lock_matches_pid wrongly
rejected a healthy watcher as dead.

On Linux, fm_pid_identity now reads /proc/<pid>/stat field 22
(starttime, in clock ticks since boot) combined with the process
command line from /proc/<pid>/cmdline - both immune to wall-clock
steps. Non-Linux platforms keep the previous ps lstart-based identity
unchanged as a fallback.

The identity is an opaque string; a lock written under the old format
reads as unhealthy once and self-heals on the next arm/restart.

Tests added to tests/fm-watcher-lock.test.sh cover: identity stability
across consecutive reads, distinctness across processes,
fm_watcher_lock_matches_pid accept/reject, and that the Linux identity
is ticks-derived rather than a rendered wall-clock date. The actual
clock-step regression cannot be reproduced in a unit test (the system
clock can't be stepped in-test); these are the honest unit-level
proxies for it.
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