use std::{
future::Future,
panic::AssertUnwindSafe,
sync::{Arc, mpsc},
};
use query_planning::error::{QueryError, QueryErrorCode, Result};
use super::{
GpuExecutionJobClass, GpuExecutionJobKind, GpuExecutionPool, JobObservation, RUN_OPERATION, job_panic_error,
};
use crate::observability::{RuntimeObservationHandle, RuntimeStreamLeaseScope};
#[derive(Clone)]
pub struct GpuExecutorLease {
pool: GpuExecutionPool,
execution_class: GpuExecutionJobClass,
}
impl GpuExecutorLease {
pub(crate) fn from_pool(pool: GpuExecutionPool) -> Self {
Self { pool, execution_class: GpuExecutionJobClass::Lifecycle }
}
pub(crate) fn from_pool_for_auxiliary_fragment(pool: GpuExecutionPool) -> Self {
Self { pool, execution_class: GpuExecutionJobClass::Execution }
}
pub async fn run<F, T>(&self, kind: GpuExecutionJobKind, future: F) -> Result<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
self.pool.run(kind, future).await
}
pub fn run_observed_scoped<B, F, T>(
&self,
kind: GpuExecutionJobKind,
observation: RuntimeObservationHandle,
build_future: B,
) -> impl Future<Output = Result<T>> + Send + 'static + use<B, F, T>
where
B: FnOnce(RuntimeStreamLeaseScope) -> F + Send + 'static,
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let class = match kind {
GpuExecutionJobKind::QueryExecution => self.execution_class,
_ => kind.job_class(),
};
self.pool.clone().run_observed_scoped_in_class(kind, class, observation, build_future)
}
pub fn submit_blocking<F, T>(
&self,
kind: GpuExecutionJobKind,
observation: RuntimeObservationHandle,
work: F,
) -> Result<PoolBlockingWork<T>>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
self.pool.submit_blocking(kind, observation, work)
}
#[doc(hidden)]
pub fn submit_blocking_with_completion<F, T, C>(
&self,
kind: GpuExecutionJobKind,
observation: RuntimeObservationHandle,
work: F,
completion: C,
) -> Result<PoolBlockingWork<T>>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
C: FnOnce() + Send + 'static,
{
self.pool.submit_blocking_with_completion(kind, observation, work, completion)
}
}
impl std::fmt::Debug for GpuExecutorLease {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.debug_struct("GpuExecutorLease").finish_non_exhaustive()
}
}
pub struct PoolBlockingWork<T> {
pub(super) receiver: mpsc::Receiver<BlockingWorkOutcome<T>>,
pub(super) job: Arc<JobObservation>,
pub(super) device_ordinal: usize,
pub(super) kind: GpuExecutionJobKind,
}
impl<T> std::fmt::Debug for PoolBlockingWork<T> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.debug_struct("PoolBlockingWork").finish_non_exhaustive()
}
}
pub(super) enum BlockingWorkOutcome<T> {
Completed(T),
Panicked,
}
pub(super) struct BlockingCompletionNotify<C: FnOnce()> {
completion: Option<C>,
}
impl<C: FnOnce()> BlockingCompletionNotify<C> {
pub(super) fn new(completion: C) -> Self {
Self { completion: Some(completion) }
}
pub(super) fn notify(&mut self) {
if let Some(completion) = self.completion.take() {
completion();
}
}
}
impl<C: FnOnce()> Drop for BlockingCompletionNotify<C> {
fn drop(&mut self) {
if self.completion.is_some() {
let _ = std::panic::catch_unwind(AssertUnwindSafe(|| self.notify()));
}
}
}
impl<T> PoolBlockingWork<T> {
pub fn join(self) -> Result<T> {
match self.receiver.recv() {
Ok(BlockingWorkOutcome::Completed(value)) => Ok(value),
Ok(BlockingWorkOutcome::Panicked) => Err(job_panic_error(self.device_ordinal, self.kind)),
Err(_) => {
self.job.cancellation_requested();
Err(QueryError::new(
QueryErrorCode::Cancelled,
RUN_OPERATION,
"GPU execution pool blocking worker ended without delivering completion",
)
.with_fact("device_ordinal", self.device_ordinal)
.with_fact("job_kind", self.kind.as_str())
.with_fact("reason", "gpu_execution_pool_blocking_worker_lost"))
},
}
}
}