Repository navigation
Conversation
There was a problem hiding this comment.
1 issue found across 12 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/runtime/src/validation/mod.rs">
<violation number="1" location="lib/runtime/src/validation/mod.rs:111">
P2: This guard is scoped to one `WorkerValidation`, not the process, so multiple `DistributedRuntime` instances can each claim a listener. With an ephemeral port this creates multiple validation endpoints; with `DYN_WORKER_VALIDATION_LOCAL_PORT` set, the later worker fails to bind. Use process-wide coordination for the one-endpoint-per-process contract.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| release_hooks: parking_lot::Mutex<Vec<ReleaseHook>>, | ||
| released: watch::Sender<bool>, | ||
| started: AtomicBool, | ||
| local_endpoint_claimed: AtomicBool, |
There was a problem hiding this comment.
P2: This guard is scoped to one WorkerValidation, not the process, so multiple DistributedRuntime instances can each claim a listener. With an ephemeral port this creates multiple validation endpoints; with DYN_WORKER_VALIDATION_LOCAL_PORT set, the later worker fails to bind. Use process-wide coordination for the one-endpoint-per-process contract.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At lib/runtime/src/validation/mod.rs, line 111:
<comment>This guard is scoped to one `WorkerValidation`, not the process, so multiple `DistributedRuntime` instances can each claim a listener. With an ephemeral port this creates multiple validation endpoints; with `DYN_WORKER_VALIDATION_LOCAL_PORT` set, the later worker fails to bind. Use process-wide coordination for the one-endpoint-per-process contract.</comment>
<file context>
@@ -108,6 +108,7 @@ pub struct WorkerValidation {
release_hooks: parking_lot::Mutex<Vec<ReleaseHook>>,
released: watch::Sender<bool>,
started: AtomicBool,
+ local_endpoint_claimed: AtomicBool,
metrics: std::sync::OnceLock<Metrics>,
}
</file context>
There was a problem hiding this comment.
Not changing this. One DistributedRuntime per worker process is the supported shape; two runtimes in one process each parse the env and each own their gates, so each gets its own endpoint. With a fixed port the second attach fails loudly at bind rather than two workers silently sharing one endpoint, which is the behaviour I'd want.
A gated worker is unreachable by design: its card is held, so no frontend routes to it. Its gates still need to send it OpenAI requests that go through the frontend's preprocessor, tool-call and reasoning parsers. When DYN_WORKER_VALIDATION_GATES is set, LocalModel::attach serves the worker's first token chat/completions base-model card on 127.0.0.1 (DYN_WORKER_VALIDATION_LOCAL_PORT, ephemeral by default), using the same pipeline as the in-process token HTTP input. The engine behind it is the worker's local engine, looked up per request in the LocalEndpointRegistry and reached with a serde round trip, the inverse of the JSON adapters workers register; until it is registered the endpoint answers 503. Gates now start only once that endpoint is serving and the engine is registered, with OPENAI_BASE_URL and DYN_VALIDATION_MODEL set, replacing the runtime's start-on-first-local-engine trigger. The endpoint closes once the worker is released. No listener opens when gates are unset. The unified Rust worker and the dyn:// token input now register a local engine when gates are set, even without a canary payload. Python workers already register one for every served endpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Seb Ringrose <seb@doubleword.ai>
2448ec3 to
f798416
Compare
|
Superseded by the cut-down stack #6 → #7 → #8 → #9 → #10 → #11 (~4,950 → ~2,000 lines): #2 is split into exec gates (#7) and sidecar url gates (#8), the url contract is now 200 = pass, and metrics, the fixed loopback port, the virtual-planner change and the parity harness are dropped. The review threads here stay for reference. 🤖 Generated with Claude Code |
Fourth of five stacked PRs for the worker validation gates DEP (
ai-dynamo/dynamo#15897). Based on #3. The code doesn't depend on #3, but the stack is kept linear so #5's end-to-end test covers the card hold and this endpoint together.Why
A gated worker is unreachable by design: its card is held, so no frontend routes to it. Its checks still need to send requests that go through the frontend's OpenAI preprocessing, tool-call and reasoning parsers.
What
lib/llm/src/entrypoint/input/local_openai.rs. WhenDYN_WORKER_VALIDATION_GATESis set,LocalModel::attachserves the worker's first base-model card that takes tokens and has a chat or completions surface. It listens on127.0.0.1:${DYN_WORKER_VALIDATION_LOCAL_PORT}, where 0 (the default) means an ephemeral port.serve_in_process_tokens, extracted fromhttp.rswith no behaviour change.LocalEndpointRegistrythroughRegistryEngine. This is a serde round trip, the inverse ofJsonProbeAdapter, so the parsers see exactly what they'd see over the request plane. The endpoint returns 503 until the engine is registered.OPENAI_BASE_URL=http://127.0.0.1:<port>/v1andDYN_VALIDATION_MODEL. This replaces feat(runtime): worker validation gates #2's start-on-first-local-engine trigger, which could also fire onload_loraorclear_kv_blocksregistrations.backend-common) and thedyn://token input (the mocker andrun_input) now register a local engine when gates are set, even without a canary payload. Python workers already register one for every served endpoint, so the bindings don't change.enable_rl.HttpServiceConfig::enable_rlis nowOption<bool>, so the loopback service can turn RL discovery off.DYN_ENABLE_RLis also a worker env var.WorkerValidation::claim_local_endpoint()ensures there's only one, even with multiple cards or LoRA attaches.Tests
lib/llm/tests/local_openai_validation.rs:loopback_matches_frontend_on_the_mockerruns withhold=health, so the frontend can see the gated worker to compare against. It starts a real mocker worker with scripted output against a real frontend (ModelWatcher+HttpService). Unary and streaming chat responses from both are identical, with reasoning (deepseek_r1) and tool calls (hermes) parsed. Gates get the right URL and model, and the endpoint closes on release. Stripping the tool parser on the loopback side makes the test fail.ungated_worker_opens_no_listener.endpoint_is_unavailable_until_the_engine_registers: 503 and gatespendinguntil the engine registers; then the gates start.local_openai.rscover card eligibility, port parsing, 503 before registration, and the JSON round trip.cargo fmt,cargo clippy -p dynamo-llm -p dynamo-runtime -p dynamo-backend-common --all-targets -D warningsandcargo check --workspaceare clean.Constraints
hostNetwork, so it isn't rejected here. UnderhostNetworkthe endpoint is reachable from the node while gates run; an operator-side check is needed.Annotatederrors rather thanLLMEngineOutputwithfinish_reason=Error. Both surface as errors, but engine-error bodies may differ slightly from the frontend's.Stack
/livefrom/health🤖 Generated with Claude Code