Skip to content

fix(epp): tolerate transient EndpointSlice port absence during startup - #13534

Open
panpan0000 wants to merge 5 commits into
ai-dynamo:mainfrom
panpan0000:fix/epp-endpointslice-transient-port-race
Open

fix(epp): tolerate transient EndpointSlice port absence during startup#13534
panpan0000 wants to merge 5 commits into
ai-dynamo:mainfrom
panpan0000:fix/epp-endpointslice-transient-port-race

Conversation

@panpan0000

@panpan0000 panpan0000 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

this is an issue when I developing #13451

Summary

The standalone EPP resolves its replica-agg peer port at startup. Resolution previously LISTed the Service's EndpointSlices and treated "an EndpointSlice does not expose the named port" as a fatal startup error. When all EPP pods restart at once, the EndpointSlice controller rewrites the slice while pods churn, so a new pod can LIST a slice mid-update that momentarily lacks the port — every replica then crashes once and is restarted by Kubernetes.

This is a pre-existing weakness in the replica-sync port resolution (surfaced while validating #13451, but independent of that feature).

Observed error (all EPP pods restarted simultaneously)

Error: resolving peer ports for EPP Service epp-sim/dynamo-epp

Caused by:
    EndpointSlice dynamo-epp-t86gq does not expose named port "replica-agg"

Each pod crashed once and recovered on the next start once the slice settled (RESTARTS: 1, then 1/1 Running).

Root fix

The Service spec — not its EndpointSlices — is the source of truth for the port list, and it never changes during pod restarts. Resolution now GETs the peer Service and validates exactly one TCP replica-agg port from spec.ports; EndpointSlices are consulted only for endpoint membership (the existing watch in spawn). A momentarily-incomplete slice can therefore never fail EPP startup — the race is gone at the source, and the earlier skip-and-retry workaround is removed.

Genuine misconfiguration still fails fast with a clear error: missing, duplicated, non-TCP, or non-positive replica-agg ports.

Changes

  • 270dec60: resolve the replica-agg port from the Service contract — resolve_replica_sync_port now GETs the Service and validates spec.ports; the bounded retry loop and skip-transient-slice logic are removed.
  • The earlier commits in this PR (56dd847c, f55d92aa) document the journey to the fix; the final state is the Service-contract resolution above.

Reviewer starting point

resolve_replica_sync_port in deploy/inference-gateway/ext-proc/src/peer_discovery.rs.

Related Issues

No dedicated issue; this was surfaced while validating #13451 and is an independent pre-existing fix.

Validation

  • cargo fmt -p dynamo-ext-proc -- --check
  • cargo test -p dynamo-ext-proc peer_discovery (15 passed, including the new resolves_from_service_contract_regardless_of_slice_state)
  • cargo clippy -p dynamo-ext-proc --no-deps --all-targets -- -D warnings

The EPP resolves its replica-agg peer port from the Service EndpointSlices at
startup and treated "a slice does not expose the named port" as fatal. The
EndpointSlice controller rewrites slices while pods churn, so restarting all
pods at once makes every new pod LIST a slice mid-update that momentarily
lacks the port: each replica then crashes once and is restarted by
Kubernetes (observed as `EndpointSlice dynamo-epp-... does not expose named
port "replica-agg"`).

The Service is the single source of truth for the port list, so a
momentarily-incomplete slice is a transient race, not a misconfiguration.
Skip slices that lack the named port and error only when no slice exposes it
or values conflict; an explicit non-TCP protocol is still rejected.

Signed-off-by: Peter Pan <Peter.Pan@daocloud.io>
@panpan0000
panpan0000 requested review from a team as code owners August 19, 2026 10:35
@copy-pr-bot

copy-pr-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@panpan0000
panpan0000 deployed to external_collaborator August 19, 2026 10:35 — with GitHub Actions Active
@panpan0000
panpan0000 deployed to external_collaborator August 19, 2026 10:35 — with GitHub Actions Active
@github-actions github-actions Bot added the fix label Aug 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

👋 Hi panpan0000! Thank you for contributing to ai-dynamo/dynamo.

Just a reminder: The NVIDIA Test Github Validation CI runs an essential subset of the testing framework to quickly catch errors.Your PR reviewers may elect to test the changes comprehensively before approving your changes.

🚀

@github-actions github-actions Bot added the external-contribution Pull request is from an external contributor label Aug 19, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment thread deploy/inference-gateway/ext-proc/src/peer_discovery.rs
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c9f3a03c-83a9-4db4-8b90-a8b141913fa4

📥 Commits

Reviewing files that changed from the base of the PR and between 7b02c65 and 56dd847.

📒 Files selected for processing (1)
  • deploy/inference-gateway/ext-proc/src/peer_discovery.rs

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.


Walkthrough

replica_sync_port now skips EndpointSlices without the named replica-agg port. It still validates matching ports and reports an error when no slice provides the port. Tests cover temporary port absence.

Changes

Replica aggregate port discovery

Layer / File(s) Summary
Resolve and validate the named port
deploy/inference-gateway/ext-proc/src/peer_discovery.rs
replica_sync_port skips slices without replica-agg, validates TCP protocol and resolved values, reports when no slice provides the port, and tests successful resolution from another slice.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🔵 Low · up to 56dd8

The change is localized and addresses a narrow startup race, but the repository-required full formatting and workspace Clippy checks still need to be completed or explicitly accepted by an owner before merge.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description covers the required topics, but it contradicts the changes by describing Service-contract resolution instead of EndpointSlice skipping and omits the required issue-path selection. Update the description to match the implemented EndpointSlice behavior and select the required Related Issues option, including the no-issue confirmation if applicable.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly states that startup should tolerate transient EndpointSlice port absence, which matches the primary change.

Comment @coderabbitai help to get the list of available commands.

…pdates

Skipping a transiently-incomplete slice still leaves the single-slice case
failing once: when the only slice is mid-update, resolution has nothing to
resolve and the EPP crashes at startup, then Kubernetes restarts it after
the slice settles.

The transient window is short (hundreds of ms), so re-LIST with a bounded
doubling backoff (5 attempts, 100ms doubling) before giving up. A genuine
misconfiguration still fails with the same clear error after retries
exhaust.

Signed-off-by: Peter Pan <Peter.Pan@daocloud.io>
@panpan0000
panpan0000 deployed to external_collaborator August 19, 2026 10:42 — with GitHub Actions Active
Add missing docstrings to raise docstring coverage on the touched file.

Signed-off-by: Peter Pan <Peter.Pan@daocloud.io>
@panpan0000
panpan0000 deployed to external_collaborator August 19, 2026 10:49 — with GitHub Actions Active
@panpan0000

Copy link
Copy Markdown
Contributor Author

Addressed both pre-merge check warnings:

  • Docstring coverage: added docstrings to resolve_replica_sync_port,
    replica_sync_port, reconcile_once, and is_ipv6 (commit 30690895).
  • Description check: added the ## Related Issues section (no dedicated
    issue; surfaced while validating feat(epp): recover embedded KV index from peers before readiness #13451) and a ## Reviewer starting point
    section pointing at resolve_replica_sync_port.

The two commits implementing the fix itself remain 56dd847c (skip
transiently-incomplete slices) and f55d92aa (bounded retry across the
transient window).

@tmonty12

Copy link
Copy Markdown
Contributor

/ok to test 3069089

tmonty12 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

I think the intended startup flow should be:

  1. Validate the peer Service contract: it defines exactly one TCP replica-agg port.
  2. Start EndpointSlice discovery and identify peers that are actually recoverable: eligible endpoint plus a resolved replica-agg port.
  3. If recoverable peers exist, use them for initial recovery.
  4. If none exist, there is no recovery source, so the EPP should become Ready and serve normally.

An EndpointSlice with endpoints but without the required named port is not a recovery source. That may be brief controller convergence, an older/incompatible peer, or a configuration problem, but it should not crash or block EPP startup. We should exclude it from the initial recovery set, log/metric it, and let normal EndpointSlice discovery incorporate it if a later update makes it usable.

This avoids making startup depend on a retry window while retaining the Service as the source of truth for the required port contract.

Resolve the replica-sync port from the peer Service's stable spec.ports
instead of LISTing EndpointSlices. Pod restarts rewrite EndpointSlices while
the Service spec never changes, so a momentarily-incomplete slice can no
longer fail EPP startup: the race this PR originally worked around with
skip-and-retry is gone at the source, and the bounded retry loop is removed.

The Service stays the single source of truth for the port list: missing,
duplicated, non-TCP, or non-positive ports still fail startup as a genuine
misconfiguration. EndpointSlices remain the discovery source for which peers
exist (spawn), never for the port number.

Rewrites the port-resolution tests against Service fixtures, including the
key assertion that resolution consults only the stable Service object, so
transient slice state can never fail startup.

Signed-off-by: Peter Pan <Peter.Pan@daocloud.io>
@panpan0000
panpan0000 deployed to external_collaborator August 21, 2026 03:34 — with GitHub Actions Active
@panpan0000

Copy link
Copy Markdown
Contributor Author

Implemented, following your intended startup flow — the port contract is now validated from the peer Service's stable spec.ports, not from the churning EndpointSlices (commit 270dec60):

  1. resolve_replica_sync_port GETs the peer Service and validates exactly one TCP replica-agg port. Duplicated, non-TCP, or non-positive ports still fail startup as a genuine misconfiguration — only the transient EndpointSlice race is gone.
  2. The bounded retry loop is removed: the Service object never changes during pod restarts, so a momentarily-incomplete slice can no longer fail or delay startup at all.
  3. EndpointSlices remain the discovery source for which peers exist (the existing watch in spawn), and feat(epp): recover embedded KV index from peers before readiness #13451's recovery path already treats "no usable peer" as bootstrap-empty (become Ready, serve normally) rather than a startup failure — covering your steps 2–4.

One note: this reads the Service port (9092), which in the shipped ClusterIP manifest equals the pods' containerPort; if those ever diverge, the targetPort would need resolving too.

@tmonty12 tmonty12 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two P1 issues in the new Service-based port resolution:

Comment thread deploy/inference-gateway/ext-proc/src/peer_discovery.rs Outdated
Comment thread deploy/inference-gateway/ext-proc/src/peer_discovery.rs Outdated
@tmonty12

Copy link
Copy Markdown
Contributor

/ok to test 270dec6

Signed-off-by: Peter Pan <Peter.Pan@daocloud.io>
@panpan0000
panpan0000 requested a review from a team as a code owner August 22, 2026 13:42
@panpan0000
panpan0000 deployed to external_collaborator August 22, 2026 13:42 — with GitHub Actions Active
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation external-contribution Pull request is from an external contributor fix size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants