use std::{
cell::{Cell, RefCell},
marker::PhantomData,
mem::ManuallyDrop,
ptr,
};
use cudf::runtime::{CudaEvent, ExecutionBindingId};
use cugraph_sys::cugraph_graph_t;
use super::{Forward, Graph, map_cudf_runtime_error};
use crate::{
cuda::{DeviceResourceKind, drop_device_resource, quarantine_device_drop},
error::{Error, ErrorKind, Result},
handle::{EnteredContext, ResourceHandleLease},
types::{DataType, GraphProperties},
};
pub struct ParkedGraph<T = Forward> {
pub(super) raw: *mut cugraph_graph_t,
pub(super) device: i32,
pub(super) properties: GraphProperties,
pub(super) vertex_dtype: DataType,
pub(super) weight_dtype: DataType,
pub(super) has_weights: bool,
pub(super) edge_id_dtype: Option<DataType>,
pub(super) park_event: CudaEvent,
pub(super) park_binding: ExecutionBindingId,
pub(super) resource_handle_leases: Vec<ResourceHandleLease>,
pub(super) _not_sync: PhantomData<Cell<()>>,
pub(super) _transposition: PhantomData<T>,
}
unsafe impl<T: Send> Send for ParkedGraph<T> {}
impl<T> ParkedGraph<T> {
pub fn device(&self) -> i32 {
self.device
}
pub fn properties(&self) -> GraphProperties {
self.properties
}
pub fn vertex_dtype(&self) -> DataType {
self.vertex_dtype
}
pub fn weight_dtype(&self) -> DataType {
self.weight_dtype
}
pub fn resume(self, handle: &EnteredContext<'_>) -> Result<Graph<T>> {
let operation = "ParkedGraph::resume";
self.validate_resume_handle(handle, operation)?;
self.resume_ordered(handle, operation)
}
pub fn resume_under(self, handle: &EnteredContext<'_>) -> Result<Graph<T>> {
let operation = "ParkedGraph::resume_under";
if handle.device() != self.device {
return Err(Error::new(ErrorKind::InvalidInput, operation)
.with_message("parked graph and resource handle must be on the same CUDA device")
.with_context("graph_device", self.device.to_string())
.with_context("handle_device", handle.device().to_string()));
}
if handle.runtime().is_none() {
return Err(Error::new(ErrorKind::InvalidState, operation)
.with_message("parked graph resume requires a runtime-bound resource handle"));
}
self.resume_ordered(handle, operation)
}
fn resume_ordered(self, handle: &EnteredContext<'_>, operation: &'static str) -> Result<Graph<T>> {
let runtime = handle.runtime().expect("validated runtime-bound handle");
runtime
.stream()
.wait_event(&self.park_event)
.map_err(|error| map_cudf_runtime_error(error, operation, "failed to wait on graph park CUDA event"))?;
let ordered_binding = runtime.binding_id();
let this = ManuallyDrop::new(self);
let resource_handle_leases = unsafe { ptr::read(&this.resource_handle_leases) };
let park_event = unsafe { ptr::read(&this.park_event) };
drop(park_event);
Ok(Graph {
raw: this.raw,
device: this.device,
properties: this.properties,
vertex_dtype: this.vertex_dtype,
weight_dtype: this.weight_dtype,
has_weights: this.has_weights,
edge_id_dtype: this.edge_id_dtype,
pending_input_completion: None,
resource_handle_leases: RefCell::new(resource_handle_leases),
ordered_binding: Cell::new(Some(ordered_binding)),
untracked_resource_handle_use: Cell::new(false),
_nosend_nosync: PhantomData,
_transposition: PhantomData,
})
}
fn validate_resume_handle(&self, handle: &EnteredContext<'_>, operation: &'static str) -> Result<()> {
if handle.device() != self.device {
return Err(Error::new(ErrorKind::InvalidInput, operation)
.with_message("parked graph and resource handle must be on the same CUDA device")
.with_context("graph_device", self.device.to_string())
.with_context("handle_device", handle.device().to_string()));
}
let Some(runtime) = handle.runtime() else {
return Err(Error::new(ErrorKind::InvalidState, operation)
.with_message("parked graph resume requires a runtime-bound resource handle"));
};
if runtime.binding_id() != self.park_binding {
return Err(Error::new(ErrorKind::InvalidState, operation)
.with_message("parked graph execution binding does not match the resume handle")
.with_context("graph_binding_id", self.park_binding.to_string())
.with_context("handle_binding_id", runtime.binding_id().to_string()));
}
Ok(())
}
}
impl<T> std::fmt::Debug for ParkedGraph<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ParkedGraph")
.field("device", &self.device)
.field("properties", &self.properties)
.field("vertex_dtype", &self.vertex_dtype)
.field("weight_dtype", &self.weight_dtype)
.field("has_weights", &self.has_weights)
.finish_non_exhaustive()
}
}
fn free_parked_graph_raw_with_leases(
raw: &mut *mut cugraph_graph_t,
device: i32,
park_event: &CudaEvent,
leases: &[ResourceHandleLease],
) {
let mut free = || match park_event.synchronize() {
Ok(()) => drop_device_resource(raw, device, "ParkedGraph::drop", DeviceResourceKind::Graph),
Err(error) => {
let owned = std::mem::replace(raw, ptr::null_mut());
if !owned.is_null() {
quarantine_device_drop("ParkedGraph::drop", device, &error);
}
},
};
if let Some(lease) = leases.first() {
lease.with_teardown_gate(free);
} else {
free();
}
}
impl<T> Drop for ParkedGraph<T> {
fn drop(&mut self) {
free_parked_graph_raw_with_leases(&mut self.raw, self.device, &self.park_event, &self.resource_handle_leases);
}
}