use std::{
mem,
sync::{PoisonError, TryLockError, atomic::Ordering},
time::Instant,
};
use query_planning::error::{QueryError, QueryErrorCode, Result};
use super::{AdmissionController, query_service_shutdown_timeout};
use crate::runtime::{
deadline::{deadline_expired, deadline_remaining, mutex_lock_recovering_until},
query_service::{
QueryServiceDrainPhase, QueryServiceDrainPhaseOutcome,
admission::{AdmissionRequest, finish_closed_queued_requests},
},
};
#[must_use]
pub(in crate::runtime::query_service) struct ClosedAdmission<'a> {
controller: &'a AdmissionController,
result: Result<()>,
closed_requests: Vec<AdmissionRequest>,
}
impl ClosedAdmission<'_> {
pub(in crate::runtime::query_service) fn finish(self) -> Result<()> {
self.controller.notify_admission_available();
finish_closed_queued_requests(self.closed_requests);
self.result
}
}
impl AdmissionController {
pub(in crate::runtime::query_service) fn close_gate(&self) {
self.admission_open.store(false, Ordering::Release);
}
pub(in crate::runtime::query_service) fn close(&self) -> ClosedAdmission<'_> {
self.close_gate();
let (result, closed_requests) = match self.state.try_lock() {
Ok(mut state) => {
state.closed = true;
let closed_requests = mem::take(&mut state.queued).into_iter().collect::<Vec<_>>();
(Ok(()), closed_requests)
},
Err(TryLockError::Poisoned(error)) => {
let mut state = error.into_inner();
state.closed = true;
let closed_requests = mem::take(&mut state.queued).into_iter().collect::<Vec<_>>();
(
Err(QueryError::new(
QueryErrorCode::Internal,
"query_engine.query_service.admission",
"query service admission mutex was poisoned",
)
.with_fact("reason", "admission_mutex_poisoned")),
closed_requests,
)
},
Err(TryLockError::WouldBlock) => (Ok(()), Vec::new()),
};
ClosedAdmission { controller: self, result, closed_requests }
}
pub(in crate::runtime::query_service) fn wait_for_quiescence_until(
&self,
deadline: Instant,
operation: &'static str,
) -> QueryServiceDrainPhaseOutcome {
let Some(mut state) = mutex_lock_recovering_until(&self.state, deadline) else {
return QueryServiceDrainPhaseOutcome::DeadlineElapsed {
phase: QueryServiceDrainPhase::AdmissionQuiescence,
error: query_service_shutdown_timeout(operation, None, "admission_mutex"),
};
};
state.closed = true;
let closed_requests = mem::take(&mut state.queued).into_iter().collect::<Vec<_>>();
if !closed_requests.is_empty() {
drop(state);
finish_closed_queued_requests(closed_requests);
state = match self.lock_admission_until(deadline, operation) {
Ok(state) => state,
Err(error) => {
return QueryServiceDrainPhaseOutcome::from_result(
QueryServiceDrainPhase::AdmissionQuiescence,
Err(error),
);
},
};
}
let mut waited_for_quiescence = false;
loop {
if state.lifecycle.is_quiescent() && state.queued.is_empty() && state.pending_preflight.is_empty() {
if waited_for_quiescence && deadline_expired(deadline) {
return QueryServiceDrainPhaseOutcome::DeadlineElapsed {
phase: QueryServiceDrainPhase::AdmissionQuiescence,
error: query_service_shutdown_timeout(operation, Some(&state), "admission_quiescence"),
};
}
return QueryServiceDrainPhaseOutcome::Quiescent { phase: QueryServiceDrainPhase::AdmissionQuiescence };
}
if deadline_expired(deadline) {
return QueryServiceDrainPhaseOutcome::DeadlineElapsed {
phase: QueryServiceDrainPhase::AdmissionQuiescence,
error: query_service_shutdown_timeout(operation, Some(&state), "admission_quiescence"),
};
}
let Some(remaining) = deadline_remaining(deadline) else {
return QueryServiceDrainPhaseOutcome::DeadlineElapsed {
phase: QueryServiceDrainPhase::AdmissionQuiescence,
error: query_service_shutdown_timeout(operation, Some(&state), "admission_quiescence"),
};
};
waited_for_quiescence = true;
#[cfg(test)]
self.shutdown_waiters.fetch_add(1, Ordering::AcqRel);
let (next, _timeout) =
self.admission_available.wait_timeout(state, remaining).unwrap_or_else(PoisonError::into_inner);
#[cfg(test)]
self.shutdown_waiters.fetch_sub(1, Ordering::AcqRel);
state = next;
}
}
}