Repository navigation
Expose worker readiness and liveness status - #229
Merged
Merged
Conversation
Checkpoint while #168 is paused for native 3.0.1 qualification. Adds Worker.status() with WorkerStatus/WorkerState/WorkerRoleStatus, per-role progress tracking, and 24 transition tests (passing locally). Docs, guide and changelog are not written yet; no PR opened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Worker.status() returns a read-only WorkerStatus snapshot for health endpoints and orchestrator probes, with WorkerState and per-role WorkerRoleStatus progress. Snapshots perform no network I/O and can be read from another thread, including while the event loop is stalled. Readiness requires this worker's own acknowledged registration, running pollers, a successful latest Server exchange per role, and no request unanswered beyond one HTTP attempt deadline. Liveness reports event-loop progress, exited pollers, and requests stuck beyond twice the transport retry budget, so a Server outage makes the worker unready but not unlive, and the next successful exchange restores readiness without a restart. Adds a stdlib probe example, a Kubernetes and shutdown guide, unit transition tests and a connected lifecycle test. Existing worker behavior is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 2, 2026
…atus tests (#168) Adds the replay regression fixture the corpus guard requires for worker.py poll-loop changes, and a test that a worker observed unready during an outage recovers readiness and completes the recorded cold history without rescheduling. Mocked empty polls now yield briefly and waits allow 10s, fixing a CI-only timing failure of the dead query thread test under full-suite load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extract run/run_until registration and role startup, the heartbeat request, and role liveness into helpers to reduce cognitive complexity without changing behavior; use math.isfinite for bound validation; split composite test assertions; drop an unused example variable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Member
Author
|
Exact head Changes since the first push:
Local at this head: full unit 3,742 passed / 4 skipped (2 bash-3.2 scanner tests deselected); ruff and mypy pass. The connected native 3.0.1 integration test passes. The outage probe still holds: unready |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #168.
What this adds
Worker.status()returns a read-onlyWorkerStatussnapshot (withWorkerStateand per-roleWorkerRoleStatus) for an application health endpoint or an orchestrator probe. Taking a snapshot never does network I/O, claims tasks, renews leases or waits on the event loop, so a probe thread can still answer when the loop is stalled.starting(registering)not_started/registering)run(), yes while registeringregistereddrainingdraining)stoppedfailedfailed:registration/runtime/shutdown)The roles are
heartbeat,workflow_poll,activity_pollandquery_poll; only configured ones are reported. Two bounds apply to an outstanding request:request_overdue_secondsaffects readiness. It defaults to one HTTP attempt deadline, so a Server outage reportsserver_unavailable:<role>even while transport retries continue.request_stall_secondsaffects liveness. It defaults to twice the client's complete transport retry budget, so an outage alone never triggers a liveness restart.Idle long polls, poll-capacity backpressure and long-running activity handlers don't affect either signal. Recovery happens on the next successful exchange, with no restart. The snapshot carries identity, states, reasons, ages and exception class names, never payloads, messages or credentials.
Also included: a stdlib probe example (
examples/worker_health_probe.py, serving/readyz,/livezand/statusfrom its own thread with SIGTERM drain), a guide (docs/guides/worker-health.md, with Kubernetes startup/readiness/liveness probes and shutdown guidance that keepsshutdown_timeout, termination grace and handler termination distinct), a reference section, a changelog entry, and the new module in the built-package smoke list.Existing worker behavior is unchanged. The only runtime additions are in-memory progress records and a one-second local tick task that does no I/O. One reorder: the pollers classify capacity backpressure (a pure check) before the existing storage-admission re-raise.
Design correction found by connected testing
My first version used one stall bound for both signals. Against a paused native Server, the outstanding poll never failed, because transport retries keep the logical request in flight. Readiness and liveness flipped together at the stall bound, which would have restarted workers during a transient outage. That split is why the overdue (readiness) and stall (liveness) bounds now exist.
Evidence (local, macOS, Python 3.12.4)
tests/test_worker_status.pyhas 24 transition tests covering not started, registering, registered, only configured roles, idle and in-flight long polls (overdue → unready but live; stalled → not live), transientConnectErrorloss and recovery without re-registration, backpressure staying ready, heartbeat failure and recovery, registration refusal, poller failure, a dead query thread, an event-loop stall observed from another thread, a long-running sync activity plus drain to stopped, failed shutdown, no I/O during snapshots, JSON shape, argument validation and default bounds. They passed on 3 consecutive runs.test_public_boundary_scanner.pytests were deselected (macOS bash 3.2 lacksmapfile).ruff check src tests,mypy src/durable_workflowandmkdocs build --strictall pass.sha256:485295819c7739d3e937a2c89b8593532de0d9c2fbc8f2e7aaddb0b45fe4b319, disposable SQLite container):tests/integration/test_worker_status.pypasses. It covers startup to ready, 5 s of idle long polls staying ready, a blocked sync activity with the query thread answering, drain, and stop.docker pause, client timeout 10 s): ready at t=0.3 s; paused at t=0.7 s;server_unavailable:workflow_pollwhile stilllivefrom t=10.7 s; still unready and live at t=15.7 s; unpaused at t=15.9 s; ready again at t=18.4 s with no restart and a single registration; a new workflow then completed;stoppedat t=19.2 s.Not covered: CI's PHP-server integration job will be the first PHP run, and there's no run against PHP 2.11.8 locally.
🤖 Generated with Claude Code