quickwit-oss/quickwit · error
`assign_jobs` should return at least one client or fail.
Error message
`assign_jobs` should return at least one client or fail.
What it means
assign_job is a convenience wrapper that asks the placement strategy to place exactly one search job and expects at least one (client, jobs) pair back. The contract is that assign_jobs either returns candidates or fails with an error; the expect documents that returning an empty iterator is a strategy bug. It fires if the placement strategy (e.g. round-robin or load-based with fallbacks) yields no candidate node for the job while reporting success.
Source
Thrown at quickwit/quickwit-search/src/search_job_placer.rs:367
.or_insert_with(|| (chosen_node.client.clone(), Vec::new()))
.1
.push(job);
}
Ok(job_assignments.into_values())
}
/// Assigns a single job to a client.
pub async fn assign_job<J: Job>(
&self,
job: J,
excluded_addrs: &HashSet<SocketAddr>,
) -> anyhow::Result<SearchServiceClient> {
let client = self
.assign_jobs(vec![job], excluded_addrs)
.await?
.next()
.map(|(client, _jobs)| client)
.expect("`assign_jobs` should return at least one client or fail.");
Ok(client)
}
}
#[derive(Debug, Clone)]
struct CandidateNode {
affinity_id: NodeId,
pub grpc_addr: SocketAddr,
pub client: SearchServiceClient,
/// Current load of this node in job-cost units. `None` means the node
/// could not be reached and should only be used as a last resort.
pub load: Option<usize>,
}
impl Hash for CandidateNode {
fn hash<H: Hasher>(&self, state: &mut H) {
self.affinity_id.hash(state);
}View on GitHub (pinned to a39730c5cd)
Solutions
- Ensure at least one eligible search node is running and not in the excluded set; check cluster membership health.
- If retrying on failures, bound excluded_addrs growth so the last attempt can reuse a previously failed node rather than excluding everything.
- If implementing a custom placement strategy, return an explicit error (e.g. anyhow::bail!("no available node")) instead of an empty Ok iterator.
Example fix
// before
.next()
.map(|(client, _jobs)| client)
.expect("`assign_jobs` should return at least one client or fail.");
// after
.next()
.map(|(client, _jobs)| client)
.ok_or_else(|| anyhow::anyhow!("no candidate node available for job placement"))? Defensive patterns
Strategy: retry
Validate before calling
let eligible = cluster_nodes.iter().filter(|n| !excluded_addrs.contains(&n.advertise_addr)).count(); assert!(eligible > 0, "no eligible node outside excluded_addrs");
Try / catch
// caller pattern
match placer.assign_job(&job, &excluded_addrs).await {
Ok(client) => client,
Err(e) if e.to_string().contains("no candidate") => retry_with_relaxed_exclusions().await,
Err(e) => return Err(e),
} Prevention
- Keep at least one search node reachable; monitor cluster membership.
- Cap excluded_addrs growth across retries so the final attempt can reuse a failed node.
- Custom placement strategies must return an error, never Ok with an empty iterator.
When it happens
Trigger: Calling assign_job when every candidate node is excluded (excluded_addrs covers the whole cluster), all nodes are unreachable/overloaded and the fallback logic yields nothing, or a custom SearchJobPlacer strategy returns Ok with an empty iterator.
Common situations: Single-node clusters where the only node's address is already in excluded_addrs after repeated retries; all search nodes marked unavailable during rolling restarts; retries exhausted (see retry_client tests) leaving no candidates.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
- failed to assign search jobs: there are no available searche
- failed to assign search jobs: there are no searcher nodes ca
- `nodes` should not be empty
- unknown tokenizer `{}` for field `{}`
- invalid named document. there are more than 1 value associat
AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08).
Data as JSON: /api/errors/dac89b6ecfb76b5a.
Report an issue: GitHub.