1use std::{
4 cell::{Cell, RefCell},
5 marker::PhantomData,
6 mem::ManuallyDrop,
7 ptr,
8};
9
10use cudf::runtime::{CudaEvent, ExecutionBindingId};
11use cugraph_sys::cugraph_graph_t;
12
13use super::{Forward, Graph, map_cudf_runtime_error};
14use crate::{
15 cuda::{DeviceResourceKind, drop_device_resource, quarantine_device_drop},
16 error::{Error, ErrorKind, Result},
17 handle::{EnteredContext, ResourceHandleLease},
18 types::{DataType, GraphProperties},
19};
20
21pub struct ParkedGraph<T = Forward> {
33 pub(super) raw: *mut cugraph_graph_t,
34 pub(super) device: i32,
35 pub(super) properties: GraphProperties,
36 pub(super) vertex_dtype: DataType,
37 pub(super) weight_dtype: DataType,
38 pub(super) has_weights: bool,
39 pub(super) edge_id_dtype: Option<DataType>,
40 pub(super) park_event: CudaEvent,
41 pub(super) park_binding: ExecutionBindingId,
45 pub(super) resource_handle_leases: Vec<ResourceHandleLease>,
46 pub(super) _not_sync: PhantomData<Cell<()>>,
47 pub(super) _transposition: PhantomData<T>,
48}
49
50unsafe impl<T: Send> Send for ParkedGraph<T> {}
55
56impl<T> ParkedGraph<T> {
57 pub fn device(&self) -> i32 {
59 self.device
60 }
61
62 pub fn properties(&self) -> GraphProperties {
64 self.properties
65 }
66
67 pub fn vertex_dtype(&self) -> DataType {
69 self.vertex_dtype
70 }
71
72 pub fn weight_dtype(&self) -> DataType {
74 self.weight_dtype
75 }
76
77 pub fn resume(self, handle: &EnteredContext<'_>) -> Result<Graph<T>> {
88 let operation = "ParkedGraph::resume";
89 self.validate_resume_handle(handle, operation)?;
90 self.resume_ordered(handle, operation)
91 }
92
93 pub fn resume_under(self, handle: &EnteredContext<'_>) -> Result<Graph<T>> {
108 let operation = "ParkedGraph::resume_under";
109 if handle.device() != self.device {
110 return Err(Error::new(ErrorKind::InvalidInput, operation)
111 .with_message("parked graph and resource handle must be on the same CUDA device")
112 .with_context("graph_device", self.device.to_string())
113 .with_context("handle_device", handle.device().to_string()));
114 }
115 if handle.runtime().is_none() {
116 return Err(Error::new(ErrorKind::InvalidState, operation)
117 .with_message("parked graph resume requires a runtime-bound resource handle"));
118 }
119 self.resume_ordered(handle, operation)
120 }
121
122 fn resume_ordered(self, handle: &EnteredContext<'_>, operation: &'static str) -> Result<Graph<T>> {
123 let runtime = handle.runtime().expect("validated runtime-bound handle");
124 runtime
125 .stream()
126 .wait_event(&self.park_event)
127 .map_err(|error| map_cudf_runtime_error(error, operation, "failed to wait on graph park CUDA event"))?;
128 let ordered_binding = runtime.binding_id();
129
130 let this = ManuallyDrop::new(self);
131 let resource_handle_leases = unsafe { ptr::read(&this.resource_handle_leases) };
134 let park_event = unsafe { ptr::read(&this.park_event) };
137 drop(park_event);
138
139 Ok(Graph {
140 raw: this.raw,
141 device: this.device,
142 properties: this.properties,
143 vertex_dtype: this.vertex_dtype,
144 weight_dtype: this.weight_dtype,
145 has_weights: this.has_weights,
146 edge_id_dtype: this.edge_id_dtype,
147 pending_input_completion: None,
148 resource_handle_leases: RefCell::new(resource_handle_leases),
149 ordered_binding: Cell::new(Some(ordered_binding)),
150 untracked_resource_handle_use: Cell::new(false),
151 _nosend_nosync: PhantomData,
152 _transposition: PhantomData,
153 })
154 }
155
156 fn validate_resume_handle(&self, handle: &EnteredContext<'_>, operation: &'static str) -> Result<()> {
157 if handle.device() != self.device {
158 return Err(Error::new(ErrorKind::InvalidInput, operation)
159 .with_message("parked graph and resource handle must be on the same CUDA device")
160 .with_context("graph_device", self.device.to_string())
161 .with_context("handle_device", handle.device().to_string()));
162 }
163
164 let Some(runtime) = handle.runtime() else {
165 return Err(Error::new(ErrorKind::InvalidState, operation)
166 .with_message("parked graph resume requires a runtime-bound resource handle"));
167 };
168 if runtime.binding_id() != self.park_binding {
169 return Err(Error::new(ErrorKind::InvalidState, operation)
170 .with_message("parked graph execution binding does not match the resume handle")
171 .with_context("graph_binding_id", self.park_binding.to_string())
172 .with_context("handle_binding_id", runtime.binding_id().to_string()));
173 }
174
175 Ok(())
176 }
177}
178
179impl<T> std::fmt::Debug for ParkedGraph<T> {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 f.debug_struct("ParkedGraph")
182 .field("device", &self.device)
183 .field("properties", &self.properties)
184 .field("vertex_dtype", &self.vertex_dtype)
185 .field("weight_dtype", &self.weight_dtype)
186 .field("has_weights", &self.has_weights)
187 .finish_non_exhaustive()
188 }
189}
190
191fn free_parked_graph_raw_with_leases(
192 raw: &mut *mut cugraph_graph_t,
193 device: i32,
194 park_event: &CudaEvent,
195 leases: &[ResourceHandleLease],
196) {
197 let mut free = || match park_event.synchronize() {
198 Ok(()) => drop_device_resource(raw, device, "ParkedGraph::drop", DeviceResourceKind::Graph),
199 Err(error) => {
200 let owned = std::mem::replace(raw, ptr::null_mut());
201 if !owned.is_null() {
202 quarantine_device_drop("ParkedGraph::drop", device, &error);
203 }
204 },
205 };
206 if let Some(lease) = leases.first() {
207 lease.with_teardown_gate(free);
208 } else {
209 free();
210 }
211}
212
213impl<T> Drop for ParkedGraph<T> {
214 fn drop(&mut self) {
215 free_parked_graph_raw_with_leases(&mut self.raw, self.device, &self.park_event, &self.resource_handle_leases);
216 }
217}