Skip to main content

vyre_driver/backend/
error.rs

1//! Actionable backend error taxonomy.
2
3/// Machine-readable classification of a backend failure kind.
4///
5/// Use this to drive retry logic, circuit breakers, and alerting rules
6/// without parsing human-readable message strings.
7#[non_exhaustive]
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum ErrorCode {
10    /// Backend device reported insufficient memory.
11    DeviceOutOfMemory,
12    /// Acquired device generation was lost or invalidated.
13    DeviceLost,
14    /// The backend does not support a required feature.
15    UnsupportedFeature,
16    /// A lock used by the backend failed to unlock safely.
17    ///
18    /// This is generally caused by a panic while a write guard was held and
19    /// indicates an internal synchronization bug in process state.
20    PoisonedLock,
21    /// GPU kernel-source compilation failed. "Shader" in the variant
22    /// name is historical; the code covers any kernel-source compile
23    /// failure for any backend kernel-source or binary validation.
24    /// A 2.0 rename to `KernelCompileFailed` is tracked in the
25    /// semver-policy doc; the variant stays stable in 0.x.
26    KernelCompileFailed,
27    /// Command dispatch or queue submission failed.
28    DispatchFailed,
29    /// The program itself is invalid for this backend.
30    InvalidProgram,
31    /// A cooperative (whole-grid-sync) launch could not fit every block
32    /// co-resident on the device. This is a routable performance condition,
33    /// not a hard failure: the orchestrator should fall back (loudly) to a
34    /// recall-identical non-cooperative path (resident fixpoint or host split).
35    CooperativeResidencyExceeded,
36    /// Unclassified error (produced by [`BackendError::new`]).
37    Unknown,
38}
39
40impl ErrorCode {
41    /// Stable integer identifier for API consumers and diagnostic catalogs.
42    ///
43    /// These ids are append-only. Existing assignments must not be reused or
44    /// renumbered because downstream systems may persist them in telemetry,
45    /// alert rules, and retry policies.
46    #[must_use]
47    pub const fn stable_id(self) -> u32 {
48        match self {
49            Self::DeviceOutOfMemory => 1001,
50            Self::UnsupportedFeature => 1002,
51            Self::PoisonedLock => 1003,
52            Self::KernelCompileFailed => 1004,
53            Self::DispatchFailed => 1005,
54            Self::InvalidProgram => 1006,
55            Self::CooperativeResidencyExceeded => 1007,
56            Self::DeviceLost => 1008,
57            Self::Unknown => 1999,
58        }
59    }
60}
61
62/// Actionable backend dispatch failure.
63///
64/// Every error that flows through the frozen `VyreBackend` contract must
65/// include remediation text beginning with `Fix: `. This guarantees that
66/// conform reports are directly actionable for backend authors and that
67/// consumers never receive an opaque failure string.
68///
69/// Use specific variants (`DeviceOutOfMemory`, `KernelCompileFailed`, etc.) when
70/// the failure class is known. [`BackendError::Other`] carries actionable failures
71/// that do not fit a structured variant.
72///
73/// # Examples
74///
75/// ```
76/// use vyre_driver::BackendError;
77///
78/// let err = BackendError::new("adapter not found. Fix: install a compatible device driver.");
79/// assert!(err.message().contains("Fix:"));
80/// ```
81#[non_exhaustive]
82#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
83pub enum BackendError {
84    /// Device ran out of memory during buffer allocation or dispatch.
85    #[error(
86        "device out of memory: requested {requested} bytes, {available} available.          Fix: reduce buffer sizes or split the dispatch into smaller chunks."
87    )]
88    DeviceOutOfMemory {
89        /// Bytes requested that triggered the OOM condition.
90        requested: u64,
91        /// Bytes reported available at the time of the failure.
92        available: u64,
93    },
94
95    /// The acquired device generation was lost and all native handles are stale.
96    #[error(
97        "device generation {generation} was lost on backend `{backend}` device `{device}`: {message}. Fix: reacquire the registered materializer and rematerialize the authenticated artifact before retrying."
98    )]
99    DeviceLost {
100        /// Registered backend identifier.
101        backend: String,
102        /// Backend-local physical or logical device identifier.
103        device: String,
104        /// Invalidated device generation.
105        generation: u64,
106        /// Concrete device-loss detail.
107        message: String,
108    },
109
110    /// The backend does not support a required feature.
111    #[error(
112        "unsupported feature `{name}` on backend `{backend}`.          Fix: check backend capability before using this feature, or select a backend that supports it."
113    )]
114    UnsupportedFeature {
115        /// Feature name (e.g. `"subgroup_ops"`, `"f16"`).
116        name: String,
117        /// Backend identifier (matches [`crate::backend::VyreBackend::id`]).
118        backend: String,
119    },
120
121    /// Internal lock poisoning was detected during backend synchronization.
122    #[error(
123        "backend lock poisoned: {lock_error}. Fix: report the panic origin, prevent panics on lock guards, and retry the backend operation."
124    )]
125    PoisonedLock {
126        /// Diagnostic details from the poison error.
127        lock_error: String,
128    },
129
130    /// GPU kernel-source compilation failed.
131    ///
132    /// "Shader" in the variant name is historical and generalised
133    ///  -  the code applies to any kernel-source compile failure across
134    /// backends. A 2.0 rename to
135    /// `KernelCompileFailed` is tracked in the semver-policy doc.
136    #[error(
137        "kernel-source compile failed on backend `{backend}`: {compiler_message} Fix: validate the vyre IR before lowering and check the lowered kernel source for type errors."
138    )]
139    KernelCompileFailed {
140        /// Backend identifier.
141        backend: String,
142        /// Compiler error text or lowered shader / IR excerpt.
143        compiler_message: String,
144    },
145
146    /// Command dispatch or GPU queue submission failed.
147    #[error(
148        "dispatch failed (code {code:?}): {message}. Fix: inspect the backend error code and queue state, reduce dispatch pressure, or reacquire the backend before retrying."
149    )]
150    DispatchFailed {
151        /// Optional backend-specific numeric error code.
152        code: Option<i32>,
153        /// Human-readable failure detail.
154        message: String,
155    },
156
157    /// Foundation validation rejected the program with a structured issue.
158    #[error("{source}")]
159    Validation {
160        /// Structured foundation-owned validation issue.
161        #[source]
162        source: vyre_foundation::validate::ValidationError,
163    },
164
165    /// The program is structurally invalid for this backend.
166    #[error("{fix}")]
167    InvalidProgram {
168        /// Actionable description, should begin with `Fix: `.
169        fix: String,
170    },
171
172    /// A cooperative whole-grid launch could not be made fully resident: the
173    /// grid has more blocks than the device can co-schedule for a grid-sync
174    /// barrier. The orchestrator must fall back (loudly) to a recall-identical
175    /// non-cooperative path rather than launch a kernel that would deadlock.
176    #[error(
177        "cooperative grid-sync launch needs {grid_blocks} co-resident block(s) but the device can fit at most {resident_limit}. Fix: route this dispatch to the resident-fixpoint or host-split grid-sync path, reduce the grid/workgroup size, or lower kernel register/shared-memory pressure. Detail: {detail}"
178    )]
179    CooperativeResidencyExceeded {
180        /// Blocks the launch geometry requires.
181        grid_blocks: u64,
182        /// Blocks the device can keep co-resident for this kernel.
183        resident_limit: u64,
184        /// Which residency bound tripped (thread vs occupancy) and the geometry.
185        detail: String,
186    },
187
188    /// Actionable backend failure without a more specific structured class.
189    #[error("{0}")]
190    Other(String),
191}
192
193impl BackendError {
194    /// Build an unclassified backend error from a complete actionable message.
195    ///
196    /// The message is preserved verbatim. Callers that can identify the
197    /// failure class use a structured variant instead.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use vyre_driver::BackendError;
203    ///
204    /// let err = BackendError::new("queue full. Fix: retry with a smaller dispatch size.");
205    /// assert_eq!(err.to_string(), "queue full. Fix: retry with a smaller dispatch size.");
206    /// ```
207    #[must_use]
208    pub fn new(message: impl Into<String>) -> Self {
209        Self::Other(message.into())
210    }
211
212    /// Build an actionable unsupported-extension error for opaque IR payloads.
213    #[must_use]
214    pub fn unsupported_extension(
215        backend: impl Into<String>,
216        extension_kind: &str,
217        debug_identity: &str,
218    ) -> Self {
219        Self::UnsupportedFeature {
220            name: format!("opaque IR extension `{extension_kind}`/`{debug_identity}`"),
221            backend: backend.into(),
222        }
223    }
224
225    /// Build a structured lock-poisoning error.
226    ///
227    /// This constructor accepts any `PoisonError` from `RwLock` operations
228    /// and returns an actionable error carrying the root poison metadata.
229    pub fn poisoned_lock<T>(error: std::sync::PoisonError<T>) -> Self {
230        Self::PoisonedLock {
231            lock_error: error.to_string(),
232        }
233    }
234
235    /// Human-readable failure message, equivalent to [`ToString::to_string`].
236    ///
237    /// Prefer explicit `match` on variants or [`ErrorCode`] for programmatic
238    /// error handling; avoid string-parsing this output.
239    #[must_use]
240    pub fn message(&self) -> String {
241        self.to_string()
242    }
243
244    /// Consume this error and return its message string.
245    ///
246    /// Useful in `map_err` chains that expect `String`.
247    #[must_use]
248    pub fn into_message(self) -> String {
249        self.to_string()
250    }
251
252    /// Machine-readable error code for programmatic error handling.
253    ///
254    /// Use this to drive retry logic, circuit breakers, and alerting
255    /// without parsing human-readable message strings.
256    #[must_use]
257    pub fn code(&self) -> ErrorCode {
258        match self {
259            Self::DeviceOutOfMemory { .. } => ErrorCode::DeviceOutOfMemory,
260            Self::DeviceLost { .. } => ErrorCode::DeviceLost,
261            Self::UnsupportedFeature { .. } => ErrorCode::UnsupportedFeature,
262            Self::PoisonedLock { .. } => ErrorCode::PoisonedLock,
263            Self::KernelCompileFailed { .. } => ErrorCode::KernelCompileFailed,
264            Self::DispatchFailed { .. } => ErrorCode::DispatchFailed,
265            Self::Validation { .. } => ErrorCode::InvalidProgram,
266            Self::InvalidProgram { .. } => ErrorCode::InvalidProgram,
267            Self::CooperativeResidencyExceeded { .. } => ErrorCode::CooperativeResidencyExceeded,
268            Self::Other(_) => ErrorCode::Unknown,
269        }
270    }
271}