Skip to content

perf(routing): bound common/HTTP candidate selection (part of #1692) - #1788

Closed
slin1237 wants to merge 1 commit into
mainfrom
perf/1692a-bound-common-selection
Closed

slin1237 wants to merge 1 commit into
mainfrom
perf/1692a-bound-common-selection

Conversation

@slin1237

@slin1237 slin1237 commented Jun 18, 2026 •

Copy link
Copy Markdown
Member

What

Part of #1692. Removes the remaining O(total fleet) per-request cost in the common/HTTP worker-selection path (WorkerSelector), which scanned the entire worker map via get_workers_filtered(None, ...) and then filtered by supports_model. (The gRPC hot path and registry.get_workers_filtered are already per-model bounded.)

Why the None full scan existed (and the correctness trap)

That None was deliberate. BasicWorker::supports_model returns true for wildcard workers (those advertising no specific models, i.e. WorkerModels::Wildcard) on any model_id, and wildcard workers are not in get_by_model(specific_model) — their model index key is a model_id label or UNKNOWN_MODEL_ID, not the requested model. So naively switching to get_by_model(model) would drop wildcard workers and cause model_not_found regressions.

Approach: bounded + wildcard-safe

How wildcards are tracked. worker_model_ids() (used to populate the model index) derives keys from worker.models(), which is empty for a wildcard, so it falls back to model_id() → a model_id label or UNKNOWN_MODEL_ID. Wildcards are therefore not reliably under one sentinel key, so I maintain an explicit set rather than rely on a key:

  • WorkerRegistry: new wildcard_workers: DashSet<WorkerId>, updated under the existing per-worker mutation lock in register_inner / replace_inner / remove_inner, keyed on the trait-level has_models_discovered() predicate (a worker is a routing wildcard exactly when it has not declared/discovered specific models).
  • get_candidates_for_model(model): returns get_by_model(model) unioned with the wildcard set, deduped by URL (a wildcard may also be model-indexed under a model_id label). Cost is O(per-model + wildcards); wildcards are few. A fast path returns the per-model slice directly (no dedup allocation) when there are no wildcards.

Hot path. get_candidates and any_worker_supports_model use the bounded lookup when the model is known (mirroring the gRPC UNKNOWN_MODEL_ID guard; UNKNOWN_MODEL_ID keeps the full-scan path). All existing filters (worker_type / connection_mode / runtime_type / availability / provider / supports_model) are still applied to the bounded set — supports_model is now a cheap safety re-check.

Fallback (never worse). If the bounded set yields no candidate, both methods fall back to the original full scan. This covers alias-only matches (a worker indexed under its id but matching the request via a ModelCard alias) and any brief index lag after a concurrent registration — so no new model_not_found.

Admin. The ?model-filtered list_workers path now uses the same bounded, wildcard-safe lookup (per-model index + wildcards) instead of get_all() + filter, with a supports_model re-filter so the listing is identical to before. The unfiltered /workers listing is left as-is (it must serialize all workers — inherently O(workers), acceptable for admin).

One-way staleness note (safe)

Membership is captured at mutation time. A wildcard worker that later discovers specific models via lazy /v1/models refresh (set_models) is not a registry mutation, so it can linger in the wildcard set until its next register/replace/remove. This is harmless: it only causes slight over-inclusion in the union, and the supports_model re-filter rejects it. The reverse (a worker that should be in the set but isn't) cannot happen — discovery is one-way (wildcard → specific; set_models is only called with a non-empty list).

Tests

  • Wildcard worker is selected for an arbitrary model via the bounded path (the core regression).
  • Per-model bounding returns the right set for a normal model and excludes other models' workers.
  • Empty-bounded-set fallback (alias-only worker) still resolves; genuinely-unknown model with no wildcard correctly returns not-found.
  • Wildcard membership updates on replace (specific ⇄ wildcard); dedup when a wildcard is also model-indexed under a label.
  • All existing worker_selection / registry / admin list_workers tests still pass.

Verification

  • cargo +nightly fmt --all — clean
  • cargo clippy -p smg --all-targets -- -D warnings (default features) — clean
  • cargo test -p smg (incl. integration tests/ binaries) — all green (lib: 1086 passed / 0 failed; spec_test 105; wasm_test 17; etc.)

Scope: registry.rs + routers/common/worker_selection.rs + the admin worker/service.rs handler only. Policy files (least_load.rs, cache_aware.rs) untouched — owned by sibling work on #1692. Not marked "Closes" since other PRs also address #1692.

Summary by CodeRabbit

  • Refactor

    • Optimized worker selection to reduce performance overhead
    • Enhanced worker model discovery handling
    • Improved efficiency of worker listing queries
  • Tests

    • Added regression tests for worker selection and discovery scenarios

The common/HTTP worker-selection path (`WorkerSelector`) scanned the whole
worker map on every request via `get_workers_filtered(None, ...)` then
filtered by `supports_model`, making candidate selection O(total fleet).
That `None` was deliberate: wildcard workers (those advertising no specific
models) accept any model_id but are not indexed under a per-model key, so a
naive switch to `get_by_model(model)` would drop them and regress servable
requests into `model_not_found`.

Add a bounded, wildcard-safe lookup and route the hot path through it:

- registry: `get_candidates_for_model(model)` returns the per-model index
  slice unioned with a tracked set of wildcard workers, deduped by URL.
  Wildcards are tracked in a `DashSet<WorkerId>` maintained under the
  per-worker mutation lock in register / replace / remove, keyed on
  `has_models_discovered()`. Cost is O(per-model + wildcards); wildcards are
  few. A fast path skips the dedup allocation entirely when no wildcards
  exist.
- common/worker_selection: `get_candidates` / `any_worker_supports_model`
  use the bounded lookup when the model is known (mirroring the gRPC
  `UNKNOWN_MODEL_ID` guard), keeping every existing filter
  (worker_type/connection_mode/runtime_type/availability/provider/
  supports_model). Fallback: if the bounded set is empty they fall back to
  the original full scan, so behavior is never worse (e.g. alias-only
  matches, or a brief index lag after registration never produce a spurious
  `model_not_found`).
- admin: the `?model`-filtered `list_workers` path uses the same bounded
  lookup (per-model index + wildcards) instead of `get_all()` + filter;
  the unfiltered listing stays O(workers), which is inherent for admin.

Tests: wildcard worker is selected for an arbitrary model via the bounded
path (the core regression); per-model bounding returns the right set and
excludes other models; empty-bounded-set fallback (alias-only) still
resolves; wildcard membership updates on replace; dedup when a wildcard is
also model-indexed. Part of #1692.

Signed-off-by: Simo Lin <25425177+slin1237@users.noreply.github.com>
@slin1237 slin1237 added enhancement New feature or request priority:high High priority labels Jun 18, 2026
@github-actions github-actions Bot added the model-gateway Model gateway crate changes label Jun 18, 2026
@coderabbitai

coderabbitai Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

WorkerRegistry gains a wildcard_workers set tracking workers with no discovered models yet. get_candidates_for_model is updated to union per-model index entries with wildcard workers. The router's get_candidates and any_worker_supports_model use this bounded lookup instead of full scans, falling back to full scan when the bounded set is empty. WorkerService::list_workers similarly switches to bounded lookup when filtering by model. Regression tests are added for all paths.

Changes

Bounded Worker Candidate Lookup

Layer / File(s) Summary
Registry: wildcard_workers field, lifecycle hooks, and bounded lookup
model_gateway/src/worker/registry.rs
WorkerRegistry gains wildcard_workers: Arc<DashSet<WorkerId>> initialized in the constructor. update_wildcard_membership is added to insert/remove a worker based on has_models_discovered(); register_inner, replace_inner, and remove_inner call it to maintain membership. get_candidates_for_model is reworked to union the per-model indexed slice with wildcard_workers and deduplicate by URL, with a fast path when no wildcard workers exist.
Router: bounded get_candidates and any_worker_supports_model
model_gateway/src/routers/common/worker_selection.rs
get_candidates now calls get_candidates_for_model for known model IDs, filters the bounded set via filter_candidates/matches_filters (request type/mode/runtime, provider, availability), and falls back to full scan only when the bounded set is empty or the model is the unknown sentinel. any_worker_supports_model tries the bounded healthy-support check first and falls back to a healthy-only full scan; healthy_supporting_candidates centralizes the shared filter+provider logic.
Service: list_workers bounded optimization
model_gateway/src/worker/service.rs
WorkerService::list_workers switches to get_candidates_for_model plus a supports_model re-filter when model is Some, deriving WorkerInfo.id via get_id_by_url; for model: None it still enumerates all workers via get_all_with_ids.
Tests: registry wildcard behavior and router bounded selection
model_gateway/src/worker/registry.rs, model_gateway/src/routers/common/worker_selection.rs
Registry tests verify wildcard union, arbitrary-model surfacing, deduplication of workers indexed under both wildcard and model key, and correct wildcard↔specific transitions during replace_inner. Router tests verify bounded-path selection for wildcard/per-model cases, fallback when bounded set is empty (alias/index lag), error behavior for unknown models, UNKNOWN_MODEL_ID full-scan wildcard resolution, and worker_type filter enforcement on bounded candidates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • lightseekorg/smg#564: Both PRs interact with Worker::supports_model and has_models_discovered in routing logic; this PR uses them during bounded candidate filtering while the retrieved PR altered their semantics via ArcSwap-backed lazy model overrides.
  • lightseekorg/smg#1672: Both PRs modify WorkerService::list_workers to add model-based filtering with wildcard/supports-model behavior; this PR is a direct optimization continuation of that filtering work.

Suggested labels

model-gateway, tests

Suggested reviewers

  • CatherineSue

Poem

🐇 Hop, hop! No more scanning every nook,
The wildcard set holds workers in a book.
Bounded by model, quick as a flash,
No full-registry sweep, no needless thrash.
The index unites with wildcards fair—
Faster routing leaps through the air! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: performance optimization of the routing layer's worker selection through bounding candidate selection, matching the PR's core objective to reduce per-request cost from O(total fleet) to O(per-model + wildcards).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/1692a-bound-common-selection

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

@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown

👋 The PR description doesn't fully follow
PULL_REQUEST_TEMPLATE.md:

  • Missing header: ## Description
  • Missing header: ### Problem
  • Missing header: ### Solution
  • Missing header: ## Changes
  • Missing header: ## Test Plan

Please update the PR description so reviewers have the context they need.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request optimizes worker selection and listing by introducing a bounded, wildcard-safe per-model lookup (get_candidates_for_model) to avoid scanning the entire fleet. Wildcard workers are tracked separately in a DashSet and unioned with model-indexed workers. The feedback identifies a performance optimization in get_candidates_for_model on the hot path: utilizing a HashSet<&str> instead of HashSet<String> to eliminate heap allocations, and collecting wildcard workers into a temporary vector first to avoid holding DashSet shard locks during lookups.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +323 to +349
pub fn get_candidates_for_model(&self, model_id: &str) -> Vec<Arc<dyn Worker>> {
let indexed = self.get_by_model(model_id);

// Fast path: no wildcard workers, so the per-model slice is the full
// candidate set. Avoids the dedup HashSet allocation entirely.
if self.wildcard_workers.is_empty() {
return indexed.to_vec();
}

let mut seen: HashSet<String> = HashSet::with_capacity(indexed.len());
let mut candidates: Vec<Arc<dyn Worker>> = Vec::with_capacity(indexed.len());
for worker in indexed.iter() {
if seen.insert(worker.url().to_string()) {
candidates.push(Arc::clone(worker));
}
}
for entry in self.wildcard_workers.iter() {
if let Some(worker) = self.get(entry.key()) {
// A wildcard worker may also be model-indexed (e.g. under a
// `model_id` label that equals `model_id`); dedup by URL.
if seen.insert(worker.url().to_string()) {
candidates.push(worker);
}
}
}
candidates
}

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.

high

On the hot path of every request, get_candidates_for_model is called to retrieve candidate workers. The current implementation allocates a new String via worker.url().to_string() for every single candidate worker and wildcard worker to perform deduplication in a HashSet<String>.

We can completely eliminate these heap allocations by using a HashSet<&str> instead. To satisfy the borrow checker, we can first collect the wildcard workers into a temporary Vec to extend their lifetime to the end of the function. This also has the added benefit of avoiding holding the DashSet shard locks on wildcard_workers while performing the self.get() lookups, preventing potential lock contention.

    pub fn get_candidates_for_model(&self, model_id: &str) -> Vec<Arc<dyn Worker>> {
        let indexed = self.get_by_model(model_id);

        // Fast path: no wildcard workers, so the per-model slice is the full
        // candidate set. Avoids the dedup HashSet allocation entirely.
        if self.wildcard_workers.is_empty() {
            return indexed.to_vec();
        }

        // Collect wildcard workers first to extend their lifetime, allowing us to
        // use a zero-allocation HashSet<&str> for deduplication. This also avoids
        // holding DashSet shard locks while performing the `self.get()` lookups.
        let wildcards: Vec<Arc<dyn Worker>> = self
            .wildcard_workers
            .iter()
            .filter_map(|entry| self.get(entry.key()))
            .collect();

        let mut seen: HashSet<&str> = HashSet::with_capacity(indexed.len() + wildcards.len());
        let mut candidates: Vec<Arc<dyn Worker>> = Vec::with_capacity(indexed.len() + wildcards.len());

        for worker in indexed.iter() {
            if seen.insert(worker.url()) {
                candidates.push(Arc::clone(worker));
            }
        }

        for worker in wildcards.iter() {
            if seen.insert(worker.url()) {
                candidates.push(Arc::clone(worker));
            }
        }

        candidates
    }
References
  1. To avoid holding DashMap shard locks during slow operations, collect the keys and/or values into a temporary collection (e.g., Vec) before iterating and performing the operations.

let worker_infos: Vec<WorkerInfo> = match model {
Some(model) => self
.worker_registry
.get_candidates_for_model(model)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Nit: get_candidates_for_model returns workers from the per-model index + wildcards, but workers reachable only via a ModelCard alias are not in the per-model index (the test empty_bounded_set_falls_back_to_full_scan in worker_selection.rs explicitly asserts this). Unlike the routing path in get_candidates, the admin endpoint has no fallback to the full scan, so list_workers?model=<alias> will always return an empty list for alias-only models — a behavioral regression from the old code which iterated get_all_with_ids() and checked supports_model on every worker.

If aliases are uncommon in practice this may be acceptable, but worth documenting or adding a full-scan fallback similar to the routing path.

Comment on lines +119 to +124
if req.model_id != UNKNOWN_MODEL_ID {
let bounded =
Self::filter_candidates(self.registry.get_candidates_for_model(req.model_id), req);
if !bounded.is_empty() {
return bounded;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Nit: The PR description notes that a stale wildcard (one that has since called set_models but stays in wildcard_workers until the next mutation) is "harmless" because supports_model re-filters. That's true downstream in find_best_worker, but the interaction with this early return is subtle: if the bounded set is non-empty only because of stale wildcards (all later rejected by supports_model), and the requested model is reachable only via alias (not indexed), the fallback to full scan never runs and the alias-only worker is never found.

The scenario is narrow (stale wildcard + alias-only model + no indexed workers for the model), but it's the one case where the "never worse" fallback guarantee doesn't hold. Worth noting in the doc comment or adding a supports_model pre-check to the emptiness test:

Suggested change
if req.model_id != UNKNOWN_MODEL_ID {
let bounded =
Self::filter_candidates(self.registry.get_candidates_for_model(req.model_id), req);
if !bounded.is_empty() {
return bounded;
}
if req.model_id != UNKNOWN_MODEL_ID {
let bounded =
Self::filter_candidates(self.registry.get_candidates_for_model(req.model_id), req);
if bounded.iter().any(|w| w.supports_model(req.model_id)) {
return bounded;
}
}

This way the fallback triggers when the bounded set has only stale wildcards, at the cost of one extra supports_model pass on the bounded set (which is cheap and already done in find_best_worker).

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good work — the bounded candidate lookup is well-reasoned and the fallback-to-full-scan approach is a sound safety net. The wildcard tracking via DashSet with mutation-time capture is clean, and the test suite is thorough (wildcard union, dedup, replace lifecycle, alias fallback, filter passthrough).

Two 🟡 nits flagged:

  • Admin list_workers?model=alias regression — the ?model path now uses the bounded lookup without a full-scan fallback, so alias-only models won't appear in the listing.
  • Stale wildcard + alias interaction — when the bounded set is non-empty only due to stale wildcards (which supports_model rejects), the full-scan fallback is skipped, and alias-only workers are missed. Narrow scenario but worth tightening the emptiness check.

No 🔴 issues. LGTM.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@model_gateway/src/routers/common/worker_selection.rs`:
- Around line 119-123: The bounded lookup optimization that filters candidates
by model ID before provider filtering can cause provider-scoped requests to be
routed incorrectly by hiding workers from other providers in the candidate set.
When a provider is specified in the request, the bounded path should be skipped
to allow the full provider-aware filtering logic to work correctly. Modify the
condition that checks `req.model_id != UNKNOWN_MODEL_ID` to also require
`req.provider.is_none()` before taking the bounded path. Apply this same fix to
both occurrences of this pattern (the one shown at startLine 119 and the other
mentioned around line 194-199).
- Around line 120-124: The early return in the bounded candidates check does not
verify that the returned candidates actually support the requested model. Before
returning the bounded set when it is non-empty, add a check to ensure at least
one candidate in bounded supports the model by filtering or checking against
supports_model. If no candidates in the bounded set support the requested model,
allow execution to continue to the full-scan alias fallback mechanism instead of
returning an unsupported candidate set. This prevents stale wildcard entries
from causing premature returns and skipping the fallback that might find a
healthy alias-only worker.

In `@model_gateway/src/worker/service.rs`:
- Around line 368-385: The current implementation of getting worker candidates
for a model in the worker service only uses the literal model index and
wildcards via get_candidates_for_model(model). When this returns an empty
bounded set, workers that support the model only through a ModelCard alias are
missed. Add a fallback mechanism similar to what the router implements: when the
initial get_candidates_for_model(model) call results in an empty set, perform a
full-scan lookup of the worker registry to find workers that support the model
via an alias. This ensures that GET /workers?model=<alias> returns results for
all servable aliases, not just literal model matches.
- Around line 378-383: The code in the worker registry lookup uses
unwrap_or_default() on the result of get_id_by_url, which creates a fabricated
default ID when the worker has been removed from the registry due to a race
condition. Instead of proceeding with this invalid default ID in the
count_and_build call, handle the None case by either skipping the stale
candidate worker entirely with a continue statement or returning an appropriate
error. Only call count_and_build when get_id_by_url successfully returns a valid
worker ID from the registry.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bd2d1a9b-0a6e-487a-a234-0be743100ed1

📥 Commits

Reviewing files that changed from the base of the PR and between a18bbdd and 24184a9.

📒 Files selected for processing (3)
  • model_gateway/src/routers/common/worker_selection.rs
  • model_gateway/src/worker/registry.rs
  • model_gateway/src/worker/service.rs

Comment on lines +119 to +123
if req.model_id != UNKNOWN_MODEL_ID {
let bounded =
Self::filter_candidates(self.registry.get_candidates_for_model(req.model_id), req);
if !bounded.is_empty() {
return bounded;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Keep provider-scoped requests on the full provider-aware path.

The bounded lookup shrinks the candidate set before filter_by_provider decides whether a multi-provider filter is needed. In a fleet with an OpenAI worker for the requested model and an Anthropic worker for another model, an Anthropic request can see only the OpenAI worker in bounded, so filter_by_provider treats it as a single-provider set and allows routing to the wrong provider. That violates the SelectWorkerRequest::provider credential-leakage guard used by the Anthropic router. A conservative fix is to skip the bounded path whenever req.provider.is_some() unless provider diversity is tracked registry-wide.

🛡️ Conservative fix
-        if req.model_id != UNKNOWN_MODEL_ID {
+        if req.model_id != UNKNOWN_MODEL_ID && req.provider.is_none() {
             let bounded =
                 Self::filter_candidates(self.registry.get_candidates_for_model(req.model_id), req);
             if !bounded.is_empty() {
                 return bounded;
             }
@@
-        if req.model_id != UNKNOWN_MODEL_ID {
+        if req.model_id != UNKNOWN_MODEL_ID && req.provider.is_none() {
             let bounded = Self::healthy_supporting_candidates(
                 self.registry.get_candidates_for_model(req.model_id),
                 req,
             );

Also applies to: 194-199

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@model_gateway/src/routers/common/worker_selection.rs` around lines 119 - 123,
The bounded lookup optimization that filters candidates by model ID before
provider filtering can cause provider-scoped requests to be routed incorrectly
by hiding workers from other providers in the candidate set. When a provider is
specified in the request, the bounded path should be skipped to allow the full
provider-aware filtering logic to work correctly. Modify the condition that
checks `req.model_id != UNKNOWN_MODEL_ID` to also require
`req.provider.is_none()` before taking the bounded path. Apply this same fix to
both occurrences of this pattern (the one shown at startLine 119 and the other
mentioned around line 194-199).

Comment on lines +120 to +124
let bounded =
Self::filter_candidates(self.registry.get_candidates_for_model(req.model_id), req);
if !bounded.is_empty() {
return bounded;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fall back when bounded candidates do not support the requested model.

get_candidates returns any non-empty bounded set before find_best_worker applies supports_model. Because stale wildcard entries are explicitly allowed, a stale-but-available wildcard can make bounded non-empty, then get rejected later by supports_model, skipping the full-scan alias fallback and missing a healthy alias-only worker.

🐛 Proposed fix
             let bounded =
                 Self::filter_candidates(self.registry.get_candidates_for_model(req.model_id), req);
-            if !bounded.is_empty() {
+            if bounded.iter().any(|w| w.supports_model(req.model_id)) {
                 return bounded;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let bounded =
Self::filter_candidates(self.registry.get_candidates_for_model(req.model_id), req);
if !bounded.is_empty() {
return bounded;
}
let bounded =
Self::filter_candidates(self.registry.get_candidates_for_model(req.model_id), req);
if bounded.iter().any(|w| w.supports_model(req.model_id)) {
return bounded;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@model_gateway/src/routers/common/worker_selection.rs` around lines 120 - 124,
The early return in the bounded candidates check does not verify that the
returned candidates actually support the requested model. Before returning the
bounded set when it is non-empty, add a check to ensure at least one candidate
in bounded supports the model by filtering or checking against supports_model.
If no candidates in the bounded set support the requested model, allow execution
to continue to the full-scan alias fallback mechanism instead of returning an
unsupported candidate set. This prevents stale wildcard entries from causing
premature returns and skipping the fallback that might find a healthy alias-only
worker.

Comment on lines +368 to +385
let worker_infos: Vec<WorkerInfo> = match model {
Some(model) => self
.worker_registry
.get_candidates_for_model(model)
.into_iter()
// The bounded set may include wildcard workers that have since
// discovered specific models; re-filter via `supports_model`
// so the listing matches the unfiltered semantics exactly.
.filter(|worker| worker.supports_model(model))
.map(|worker| {
let id = self
.worker_registry
.get_id_by_url(worker.url())
.map(|id| id.as_str().to_string())
.unwrap_or_default();
count_and_build(id, &worker)
})
.collect(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mirror the router fallback for alias-only model matches.

get_candidates_for_model(model) only looks up the literal model index plus wildcards, so workers that support model only via a ModelCard alias are omitted when the bounded set is empty. The router handles this with a full-scan fallback; the admin ?model= path should do the same or GET /workers?model=<alias> regresses to an empty listing for servable aliases.

🐛 Proposed fix
-        let worker_infos: Vec<WorkerInfo> = match model {
-            Some(model) => self
-                .worker_registry
-                .get_candidates_for_model(model)
-                .into_iter()
-                // The bounded set may include wildcard workers that have since
-                // discovered specific models; re-filter via `supports_model`
-                // so the listing matches the unfiltered semantics exactly.
-                .filter(|worker| worker.supports_model(model))
-                .map(|worker| {
-                    let id = self
-                        .worker_registry
-                        .get_id_by_url(worker.url())
-                        .map(|id| id.as_str().to_string())
-                        .unwrap_or_default();
-                    count_and_build(id, &worker)
-                })
-                .collect(),
+        let worker_infos: Vec<WorkerInfo> = match model {
+            Some(model) => {
+                let mut workers: Vec<_> = self
+                    .worker_registry
+                    .get_candidates_for_model(model)
+                    .into_iter()
+                    .filter(|worker| worker.supports_model(model))
+                    .filter_map(|worker| {
+                        self.worker_registry
+                            .get_id_by_url(worker.url())
+                            .map(|id| (id, worker))
+                    })
+                    .collect();
+
+                if workers.is_empty() {
+                    workers = self
+                        .worker_registry
+                        .get_all_with_ids()
+                        .into_iter()
+                        .filter(|(_, worker)| worker.supports_model(model))
+                        .collect();
+                }
+
+                workers
+                    .into_iter()
+                    .map(|(worker_id, worker)| count_and_build(worker_id.as_str().to_string(), &worker))
+                    .collect()
+            }
             None => self
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@model_gateway/src/worker/service.rs` around lines 368 - 385, The current
implementation of getting worker candidates for a model in the worker service
only uses the literal model index and wildcards via
get_candidates_for_model(model). When this returns an empty bounded set, workers
that support the model only through a ModelCard alias are missed. Add a fallback
mechanism similar to what the router implements: when the initial
get_candidates_for_model(model) call results in an empty set, perform a
full-scan lookup of the worker registry to find workers that support the model
via an alias. This ensures that GET /workers?model=<alias> returns results for
all servable aliases, not just literal model matches.

Comment on lines +378 to +383
let id = self
.worker_registry
.get_id_by_url(worker.url())
.map(|id| id.as_str().to_string())
.unwrap_or_default();
count_and_build(id, &worker)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Do not emit a default worker ID on lookup races.

If a worker is removed after the bounded candidate snapshot but before get_id_by_url, unwrap_or_default() returns an invalid/default ID in the API response. Skip that stale candidate or return an error instead; WorkerInfo.id should never be fabricated.

🐛 Proposed fix
-                .map(|worker| {
-                    let id = self
-                        .worker_registry
-                        .get_id_by_url(worker.url())
-                        .map(|id| id.as_str().to_string())
-                        .unwrap_or_default();
-                    count_and_build(id, &worker)
+                .filter_map(|worker| {
+                    let id = self.worker_registry.get_id_by_url(worker.url())?;
+                    Some(count_and_build(id.as_str().to_string(), &worker))
                 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let id = self
.worker_registry
.get_id_by_url(worker.url())
.map(|id| id.as_str().to_string())
.unwrap_or_default();
count_and_build(id, &worker)
.filter_map(|worker| {
let id = self.worker_registry.get_id_by_url(worker.url())?;
Some(count_and_build(id.as_str().to_string(), &worker))
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@model_gateway/src/worker/service.rs` around lines 378 - 383, The code in the
worker registry lookup uses unwrap_or_default() on the result of get_id_by_url,
which creates a fabricated default ID when the worker has been removed from the
registry due to a race condition. Instead of proceeding with this invalid
default ID in the count_and_build call, handle the None case by either skipping
the stale candidate worker entirely with a continue statement or returning an
appropriate error. Only call count_and_build when get_id_by_url successfully
returns a valid worker ID from the registry.

@slin1237 slin1237 closed this Jun 18, 2026
@lightseek-bot
lightseek-bot deleted the perf/1692a-bound-common-selection branch June 18, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request model-gateway Model gateway crate changes priority:high High priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant