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}