fleetd #544: progress watchdog for StatusPoller and SessionReaper loops #559
Reference in New Issue
Block a user
Delete Branch "worker/544-loop-progress-watchdog-9f60b3-3"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
fleetd #544 — observability half only
StatusPoller.loop and SessionReaper.loop can die or get permanently parked
(a herdr call with no read timeout can block the thread forever while
Thread.isAlive() stays true), and nothing observes it: /healthz stays
green, fleet_list/fleet_status report nothing, only one ERROR log line
exists.
What changed
LoopWatchdog(dev.ltms.fleet.inject) tracks a monotoniclast-completed-round timestamp and classifies health as one of three
states — never two, per the fleetd #512 shape (one flag cannot carry both
"halted on purpose" and "halted unexpectedly"):
RUNNING— last round finished recently.STALLED— dead or parked. Indistinguishable from outside; neverobserved on purpose.
STOPPED—stop()was called. Never an alarm.StatusPollerandSessionReapereach hold aLoopWatchdog, callrecordRoundComplete()once per completed round,reset()instart(),markStoppedByCaller()instop(), and expose the fact via a new publichealth()method.a documented multiplier: StatusPoller 40x (250ms prod interval -> 10s),
SessionReaper 12x (5000ms default interval -> 60s).
LoopWatchdoglives indev.ltms.fleet.inject, not the more obviousdev.ltms.fleet.health— see its javadoc:healthalready depends onsession, andsessionalready depends oninject, sohealthwouldhave closed a package cycle through
session. Caught byPackageCyclesTestduring development, fixed by choosinginjectinstead.
Explicitly out of scope (per the ticket's own comment)
/healthzor REST/MCP beyond the new publichealth()API — a defensible scope decision, not left undone.
catch(Throwable)decision (#543, alreadymerged) or a process-wide uncaught-exception handler (ruled out on #538).
(
UnixSocketHerdrClient.call()) — a separate, real, wanted ticket; thiswatchdog's whole reason to exist is that that deadline does not exist yet.
Tests
4 new test files, 13 new tests total:
LoopWatchdogTest(5): the three-state contract in isolation.StatusPollerWatchdogTest(4): parked-in-a-blocking-herdr-call ->STALLED (test double blocks via
CountDownLatch.await(), never a realsocket), died -> STALLED, stopped -> STOPPED not STALLED, healthy loop
stays RUNNING over real elapsed time.
SessionReaperWatchdogTest(4): same four shapes, parked case simulatedby blocking on
SessionManager's own injectable clock insidereapIdle().Every new test was mutation-tested: a targeted, line-anchored
sedmutation was applied, shown red with the test's own assertion message,
reverted, confirmed byte-identical via
shasum -a 256, and re-verifiedgreen. Full detail in the worker's handoff.
Full suite:
mvn -q test— Tests run: 1737, Failures: 0, Errors: 0,Skipped: 0.
PackageCyclesTestpasses (the package placement above is whatmakes that so).