fix(health): make canary sole authority on endpoint readiness when enabled - #8165
Conversation
…abled When DYN_HEALTH_CHECK_ENABLED=true, transport endpoints (push, HTTP, TCP) no longer eagerly set endpoints to Ready at startup. The canary health check becomes the sole authority, only marking Ready after verifying end-to-end request handling. When disabled (default), behavior unchanged. Fixes the crash loop where push_endpoint set Ready, then canary overrode to NotReady, causing liveness probe failure (failureThreshold=1). DIS-1185 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WalkthroughThe pull request adds a new Python integration test for canary health-check race conditions (DIS-1185) and modifies the health status initialization logic to conditionally defer the "Ready" status when health canary checks are enabled. The Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/runtime/src/system_health.rs (1)
69-90:⚠️ Potential issue | 🟠 MajorForce initial endpoint state to
NotReadyin canary mode.
SystemHealth::new(...)still seedsendpoint_healthfromstarting_health_status. If that status is everReady,/healthcan come up green before the first canary run, which breaks the new “canary is the sole authority” guarantee. Whenhealth_check_enabledistrue, initialize endpoint entries asNotReadyregardless ofstarting_health_status.Suggested fix
- let mut endpoint_health = HashMap::new(); - for endpoint in &use_endpoint_health_status { - endpoint_health.insert(endpoint.clone(), starting_health_status.clone()); - } + let initial_endpoint_status = if health_check_enabled { + HealthStatus::NotReady + } else { + starting_health_status.clone() + }; + + let mut endpoint_health = HashMap::new(); + for endpoint in &use_endpoint_health_status { + endpoint_health.insert(endpoint.clone(), initial_endpoint_status.clone()); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/runtime/src/system_health.rs` around lines 69 - 90, In SystemHealth::new, the endpoint_health map is currently seeded from starting_health_status; when health_check_enabled is true this can expose endpoints as Ready before canary runs. Change the seeding logic in the constructor (where endpoint_health is populated from use_endpoint_health_status) to set each endpoint's initial status to NotReady whenever health_check_enabled == true, otherwise keep using starting_health_status; update the code that inserts into endpoint_health (the loop that fills endpoint_health in SystemHealth::new) to choose NotReady vs starting_health_status accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/bindings/python/tests/test_canary_health_race.py`:
- Around line 33-61: The module currently picks a port at import time via
_get_free_port and mutates os.environ (setting _SYSTEM_PORT, DYN_SYSTEM_PORT,
DYN_HEALTH_CHECK_ENABLED, DYN_CANARY_WAIT_TIME,
DYN_HEALTH_CHECK_REQUEST_TIMEOUT) which causes cross-test leakage and race
conditions; instead move this logic into a test-scoped fixture that uses the
repository dynamic-port fixtures (runtime_services_dynamic_ports and
dynamo_dynamic_ports) and monkeypatch to set the env vars per-test, and stop
using the module-level _SYSTEM_PORT — generate the dynamic port(s) inside the
fixture, monkeypatch os.environ keys there, and ensure tests depend on that
fixture so each test gets isolated env and ports.
- Around line 63-92: The test is missing the suite scheduling marker and an
explicit timeout; add the required scheduling marker to the module-level
pytestmark list (alongside pytest.mark.gpu_0 and pytest.mark.integration) and
annotate the test_canary_overrides_transport_ready coroutine with an explicit
timeout decorator (e.g., `@pytest.mark.timeout`(30)) so the polling/network/sleep
work in never_responds and _poll_health cannot hang the test run.
---
Outside diff comments:
In `@lib/runtime/src/system_health.rs`:
- Around line 69-90: In SystemHealth::new, the endpoint_health map is currently
seeded from starting_health_status; when health_check_enabled is true this can
expose endpoints as Ready before canary runs. Change the seeding logic in the
constructor (where endpoint_health is populated from use_endpoint_health_status)
to set each endpoint's initial status to NotReady whenever health_check_enabled
== true, otherwise keep using starting_health_status; update the code that
inserts into endpoint_health (the loop that fills endpoint_health in
SystemHealth::new) to choose NotReady vs starting_health_status accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fe1fc577-8184-49e4-bfb8-f09023acbfa4
📒 Files selected for processing (6)
lib/bindings/python/tests/test_canary_health_race.pylib/runtime/src/distributed.rslib/runtime/src/pipeline/network/ingress/http_endpoint.rslib/runtime/src/pipeline/network/ingress/push_endpoint.rslib/runtime/src/pipeline/network/ingress/shared_tcp_endpoint.rslib/runtime/src/system_health.rs
- Add teardown_module to test_canary_health_race.py to restore env vars after test runs, preventing DYN_HEALTH_CHECK_ENABLED=true from leaking into other test modules (caused MM router e2e test failures) - Force initial endpoint health to NotReady when health_check_enabled=true so endpoints can't appear healthy before canary verification Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-condition-for-canary-health-check
Address CodeRabbit review: add pytest.mark.timeout(30) and pytest.mark.pre_merge markers per repo convention. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test sets module-level env vars (DYN_HEALTH_CHECK_ENABLED, DYN_SYSTEM_PORT) that leak to other tests in the same pytest session. teardown_module doesn't help because the env vars are set at import time before other modules are collected. The Rust-side fix is validated by cargo test. This Python e2e test can be re-added once the test infrastructure supports proper isolation (e.g., subprocess-based or pytest-forked). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backward Compatibility AnalysisVerified all scenarios with
Minor note: The transport endpoints acquire the |
Address review feedback from @grahamking: - Extract set_ready() method that encapsulates the health_check_enabled check and set_endpoint_health_status in a single lock scope - All three transport endpoints (push, http, tcp) now call system_health.lock().set_ready(&endpoint_name) instead of acquiring the lock twice Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clearer intent: signals that an endpoint's transport has registered, which may set Ready as a side effect (when canary is disabled). Doesn't imply unconditional readiness. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep only non-obvious WHY comments. Remove comments that restate what well-named code already communicates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…abled (ai-dynamo#8165) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…abled (#8165) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
Summary
DYN_HEALTH_CHECK_ENABLED=true, transport endpoints (push, HTTP, TCP) no longer eagerly set endpoints toReadyat startupReadyafter verifying end-to-end request handlingDYN_HEALTH_CHECK_ENABLED=false(current default), behavior is unchangedMotivation
Enabling canary health checks in k8s causes crash loops (DIS-1185). Root cause:
push_endpointeagerly setsReadywhen its NATS subscription starts, then the canary health check fires, fails (discovery not yet propagated), and overrides toNotReady. Since/liveand/healthshare the same handler, the liveness probe (failureThreshold=1) restarts the pod immediately.This fix eliminates the race by making the canary the single writer. The startup probe's 2-hour window (
failureThreshold=720) provides ample time for the canary to verify the endpoint before k8s transitions to liveness probes.Changes
system_health.rs: Addhealth_check_enabledfield and getter toSystemHealthdistributed.rs: Passconfig.health_check_enabledtoSystemHealth::new()push_endpoint.rs,http_endpoint.rs,shared_tcp_endpoint.rs: Gate eagerset_endpoint_health_status(Ready)on!health_check_enabled()test_canary_health_race.py: Python repro test proving the fixTest plan
cargo fmt --check— passcargo clippy --workspace— 0 warningscargo test -p dynamo-runtime— all passtest_canary_health_race.py) — verifies endpoint stays NotReady until canary succeedsDYN_HEALTH_CHECK_ENABLED=true— no crash loopRelated
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
Bug Fixes
Tests