Skip to main content

j2k_cuda_runtime/
error.rs

1use crate::driver::CuResult;
2
3/// Error returned by CUDA driver and J2K CUDA kernel helpers.
4#[derive(Debug, thiserror::Error)]
5#[non_exhaustive]
6pub enum CudaError {
7    /// CUDA driver library or device is unavailable.
8    #[error("CUDA driver is unavailable: {message}")]
9    Unavailable {
10        /// Human-readable availability failure.
11        message: String,
12    },
13    /// CUDA Driver API call failed.
14    #[error("CUDA driver call {operation} failed with CUresult {code}{name}")]
15    Driver {
16        /// Driver operation name.
17        operation: &'static str,
18        /// Raw CUDA result code.
19        code: CuResult,
20        /// CUDA error name, when available.
21        name: String,
22    },
23    /// Host output buffer is too small for a device download.
24    #[error("CUDA copy output buffer too small: required {required}, have {have}")]
25    OutputTooSmall {
26        /// Required byte count.
27        required: usize,
28        /// Provided byte count.
29        have: usize,
30    },
31    /// Byte length cannot be represented by the kernel ABI.
32    #[error("CUDA byte length is too large for kernel launch: {len}")]
33    LengthTooLarge {
34        /// Byte length.
35        len: usize,
36    },
37    /// A host-side allocation needed by a CUDA operation could not be reserved.
38    #[error("CUDA host allocation failed for {bytes} bytes")]
39    HostAllocationFailed {
40        /// Requested host allocation size in bytes.
41        bytes: usize,
42    },
43    /// Simultaneously live host allocations would exceed the codec policy.
44    #[error(
45        "CUDA host allocation for {what} is too large: requested {requested} bytes, cap {cap} bytes"
46    )]
47    HostAllocationTooLarge {
48        /// Aggregate requested host byte count, saturated on overflow.
49        requested: usize,
50        /// Maximum permitted simultaneously live host bytes.
51        cap: usize,
52        /// Logical operation requiring the allocation.
53        what: &'static str,
54    },
55    /// Device byte length is not aligned to the requested typed view element.
56    #[error("CUDA buffer length {bytes} is not a multiple of typed element size {element_size}")]
57    LengthNotElementAligned {
58        /// Byte length.
59        bytes: usize,
60        /// Requested element size.
61        element_size: usize,
62    },
63    /// Image dimensions overflowed allocation or launch geometry.
64    #[error("CUDA image allocation size overflow for {width}x{height}x{channels}")]
65    ImageTooLarge {
66        /// Image width.
67        width: u32,
68        /// Image height.
69        height: u32,
70        /// Channel count.
71        channels: usize,
72    },
73    /// Internal runtime state lock was poisoned.
74    #[error("CUDA runtime state lock is poisoned: {message}")]
75    StatePoisoned {
76        /// Poison error message.
77        message: String,
78    },
79    /// A CUDA operation failed and the follow-up completion check also failed.
80    #[error(
81        "CUDA operation failed ({primary}); context completion could not be established ({completion})"
82    )]
83    CompletionFailed {
84        /// Error returned by the original CUDA operation.
85        primary: Box<CudaError>,
86        /// Error returned while establishing context-wide completion.
87        completion: Box<CudaError>,
88    },
89    /// A CUDA operation failed and releasing its retained resources also failed.
90    #[error(
91        "CUDA operation failed ({primary}); retained resource release also failed ({release})"
92    )]
93    ResourceReleaseFailed {
94        /// Error returned by the original CUDA operation.
95        primary: Box<CudaError>,
96        /// Error returned while releasing retained resources.
97        release: Box<CudaError>,
98    },
99    /// A J2K CUDA kernel reported a validated runtime failure.
100    #[error("CUDA kernel {kernel} reported status {code} detail {detail}")]
101    KernelStatus {
102        /// Kernel entry point or logical stage name.
103        kernel: &'static str,
104        /// Kernel-defined status code.
105        code: u32,
106        /// Kernel-defined detail code.
107        detail: u32,
108    },
109    /// A batched J2K CUDA kernel reported a failure for one descriptor.
110    #[error("CUDA kernel {kernel} reported status {code} detail {detail} for job {job_index}")]
111    KernelJobStatus {
112        /// Kernel entry point or logical stage name.
113        kernel: &'static str,
114        /// Zero-based descriptor index within this kernel launch.
115        job_index: usize,
116        /// Kernel-defined status code.
117        code: u32,
118        /// Kernel-defined detail code.
119        detail: u32,
120    },
121    /// Caller supplied arguments that cannot be represented by this runtime API.
122    #[error("CUDA invalid argument: {message}")]
123    InvalidArgument {
124        /// Human-readable validation failure.
125        message: String,
126    },
127    /// Validated host-side planning state contradicted materialized launch data.
128    #[error("CUDA internal invariant failed: {what}")]
129    InternalInvariant {
130        /// Stable description of the failed invariant.
131        what: &'static str,
132    },
133}
134
135impl CudaError {
136    /// True when the error means the CUDA driver or device is unavailable.
137    pub fn is_unavailable(&self) -> bool {
138        match self {
139            Self::Unavailable { .. } => true,
140            Self::CompletionFailed {
141                primary,
142                completion,
143            } => primary.is_unavailable() && completion.is_unavailable(),
144            _ => false,
145        }
146    }
147
148    /// Whether an error can mean that previously submitted CUDA work still
149    /// references caller-owned allocations.
150    ///
151    /// External-runtime adapters use this conservative classification to
152    /// quarantine allocations instead of freeing storage after an uncertain
153    /// completion boundary. Pure validation, capacity, and kernel-status
154    /// errors return `false`.
155    #[doc(hidden)]
156    pub fn completion_is_uncertain(&self) -> bool {
157        matches!(
158            self,
159            Self::Driver { .. }
160                | Self::StatePoisoned { .. }
161                | Self::CompletionFailed { .. }
162                | Self::ResourceReleaseFailed { .. }
163        )
164    }
165
166    /// Descriptor index reported by a batched kernel status, when available.
167    #[doc(hidden)]
168    pub fn kernel_job_index(&self) -> Option<usize> {
169        match self {
170            Self::KernelJobStatus { job_index, .. } => Some(*job_index),
171            Self::CompletionFailed { primary, .. }
172            | Self::ResourceReleaseFailed {
173                primary,
174                release: _,
175            } => primary.kernel_job_index(),
176            _ => None,
177        }
178    }
179
180    /// Whether the retained runtime session must not execute later groups.
181    ///
182    /// Validated argument, capacity, and kernel-status errors are scoped to
183    /// the affected submission. Driver, poisoned-state, and uncertain cleanup
184    /// errors can invalidate ordering or retained resources and therefore
185    /// terminate a persistent batch call.
186    #[doc(hidden)]
187    pub fn session_is_unusable(&self) -> bool {
188        matches!(
189            self,
190            Self::Unavailable { .. }
191                | Self::Driver { .. }
192                | Self::StatePoisoned { .. }
193                | Self::CompletionFailed { .. }
194                | Self::ResourceReleaseFailed { .. }
195        )
196    }
197}
198
199pub(crate) fn select_uncertain_completion_error(
200    primary_error: CudaError,
201    completion_error: Option<CudaError>,
202) -> CudaError {
203    if let Some(completion_error) = completion_error {
204        return CudaError::CompletionFailed {
205            primary: Box::new(primary_error),
206            completion: Box::new(completion_error),
207        };
208    }
209    if matches!(
210        &primary_error,
211        CudaError::Driver { .. }
212            | CudaError::StatePoisoned { .. }
213            | CudaError::CompletionFailed { .. }
214            | CudaError::ResourceReleaseFailed { .. }
215    ) {
216        return primary_error;
217    }
218    CudaError::StatePoisoned {
219        message: format!(
220            "CUDA context became poisoned while the preceding operation failed: {primary_error}"
221        ),
222    }
223}
224
225pub(crate) fn select_resource_release_error(
226    primary_error: CudaError,
227    release_error: CudaError,
228) -> CudaError {
229    CudaError::ResourceReleaseFailed {
230        primary: Box::new(primary_error),
231        release: Box::new(release_error),
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::{select_resource_release_error, CudaError};
238
239    fn unavailable(message: &str) -> CudaError {
240        CudaError::Unavailable {
241            message: message.to_string(),
242        }
243    }
244
245    fn driver(operation: &'static str) -> CudaError {
246        CudaError::Driver {
247            operation,
248            code: 1,
249            name: "CUDA_ERROR_TEST".to_string(),
250        }
251    }
252
253    #[test]
254    fn mixed_completion_failure_is_not_fallback_eligible_unavailability() {
255        let mixed = CudaError::CompletionFailed {
256            primary: Box::new(unavailable("driver missing")),
257            completion: Box::new(driver("cuCtxSynchronize")),
258        };
259        assert!(!mixed.is_unavailable());
260
261        let unavailable_only = CudaError::CompletionFailed {
262            primary: Box::new(unavailable("driver missing")),
263            completion: Box::new(unavailable("completion unavailable")),
264        };
265        assert!(unavailable_only.is_unavailable());
266    }
267
268    #[test]
269    fn resource_release_failure_preserves_both_diagnostics_and_blocks_fallback() {
270        let error = select_resource_release_error(
271            unavailable("primary unavailable"),
272            driver("release pool hold"),
273        );
274        assert!(!error.is_unavailable());
275        assert!(matches!(error, CudaError::ResourceReleaseFailed { .. }));
276        let rendered = error.to_string();
277        assert!(rendered.contains("primary unavailable"));
278        assert!(rendered.contains("release pool hold"));
279    }
280
281    #[test]
282    fn completion_uncertainty_excludes_preflight_and_validated_kernel_status() {
283        assert!(!CudaError::InvalidArgument {
284            message: "preflight".to_string(),
285        }
286        .completion_is_uncertain());
287        assert!(!CudaError::KernelStatus {
288            kernel: "test",
289            code: 1,
290            detail: 2,
291        }
292        .completion_is_uncertain());
293        assert!(driver("cuEventSynchronize").completion_is_uncertain());
294    }
295}