use crate::error::ServerError;
use super::capacity::worker_is_at_capacity;
use super::{
ActivityKey, ConnectedWorkerRegistry, PoolAddress, RegistryState, WorkerHandle, WorkerId,
};
impl ConnectedWorkerRegistry {
pub fn select_and_reserve(
&self,
namespace: &str,
task_queue: &str,
activity_type: &str,
node: Option<&str>,
) -> Result<Option<(WorkerHandle, DispatchReservation)>, ServerError> {
let mut state = self.state()?;
let key = ActivityKey::new(PoolAddress::new(namespace, task_queue), activity_type);
let Some(worker) = eligible_candidates_in_rotation(&mut state, key, node)
.into_iter()
.next()
else {
return Ok(None);
};
let worker_id = worker.id();
let held = state.in_flight.entry(worker_id).or_insert(0);
*held = held.saturating_add(1);
drop(state);
Ok(Some((
worker,
DispatchReservation {
registry: self.clone(),
worker_id,
committed: false,
},
)))
}
pub(in crate::worker) fn reserve_worker(
&self,
worker_id: WorkerId,
) -> Result<Option<DispatchReservation>, ServerError> {
{
let mut state = self.state()?;
let advertised = match state.workers.get(&worker_id) {
Some(worker) => match worker.max_concurrency {
Some(advertised) => advertised,
None => return Ok(None),
},
None => return Ok(None),
};
let held = state.in_flight.get(&worker_id).copied().unwrap_or(0);
if held >= advertised.get() {
return Ok(None);
}
state.in_flight.insert(worker_id, held.saturating_add(1));
}
Ok(Some(DispatchReservation {
registry: self.clone(),
worker_id,
committed: false,
}))
}
}
#[must_use = "dropping the reservation immediately releases the slot it was taken to hold"]
#[derive(Debug)]
pub struct DispatchReservation {
registry: ConnectedWorkerRegistry,
worker_id: WorkerId,
committed: bool,
}
impl DispatchReservation {
#[must_use]
pub fn worker_id(&self) -> WorkerId {
self.worker_id
}
pub(in crate::worker) fn commit(mut self) {
self.committed = true;
}
}
impl Drop for DispatchReservation {
fn drop(&mut self) {
if self.committed {
return;
}
if let Err(error) = self.registry.record_dispatch_finished(self.worker_id) {
tracing::error!(
worker_id = ?self.worker_id,
%error,
"dispatch reservation could not release its capacity slot; this worker's \
selection capacity is now under-reported by one until it re-registers"
);
}
}
}
pub(super) fn eligible_candidates_in_rotation(
state: &mut RegistryState,
key: ActivityKey,
node: Option<&str>,
) -> Vec<WorkerHandle> {
let mut workers: Vec<WorkerHandle> = state
.by_activity
.get(&key)
.map(|workers| {
workers
.values()
.filter(|worker| worker_matches_node(worker, node))
.filter(|worker| !state.dispatch_ineligible.contains_key(&worker.id))
.filter(|worker| !worker_is_at_capacity(state, worker))
.cloned()
.collect()
})
.unwrap_or_default();
if workers.is_empty() {
return workers;
}
workers.sort_by_key(WorkerHandle::id);
let cursor = state.rotation.entry(key).or_insert(0);
let start = *cursor % workers.len();
*cursor = cursor.wrapping_add(1);
let mut rotated = Vec::with_capacity(workers.len());
rotated.extend_from_slice(&workers[start..]);
rotated.extend_from_slice(&workers[..start]);
rotated
}
pub(super) fn worker_matches_node(worker: &WorkerHandle, node: Option<&str>) -> bool {
match node {
None => true,
Some(node) => worker.node() == Some(node),
}
}