tenferro_tensor/session_entry.rs
1//! Typed admission failures for backend session entry.
2//!
3//! [`BackendSessionHost::with_backend_session`](crate::BackendSessionHost::with_backend_session)
4//! returns [`SessionEntryError`] when a backend refuses to open a session. Every
5//! variant is reported *before* the session callback runs, so a caller that sees
6//! this error knows the callback was not executed. A callback's own result is
7//! returned inside the `Ok` value and is never folded into this type.
8//!
9//! # Examples
10//!
11//! ```rust
12//! use tenferro_tensor::{ErrorKind, SessionEntryError};
13//!
14//! let error = SessionEntryError::Reentered { backend: "CpuBackend" };
15//! assert_eq!(error.kind(), ErrorKind::RuntimeState);
16//! assert_eq!(error.backend(), "CpuBackend");
17//! ```
18
19use tenferro_tensor_core::ErrorKind;
20
21use crate::BoxError;
22
23/// Why a backend refused to open an execution session.
24///
25/// The session callback has not run when this error is returned. Contention
26/// that the backend can wait out (another thread holding an overlapping CPU
27/// resource permit) is waited for, not reported; this type covers only states
28/// that waiting cannot resolve.
29///
30/// # Examples
31///
32/// ```rust
33/// use tenferro_tensor::{Error, ErrorKind, SessionEntryError};
34///
35/// let entry = SessionEntryError::IncompatibleContext {
36/// backend: "CpuBackend",
37/// message: "operation backend does not match the active execution scope".into(),
38/// };
39/// let error = Error::from(entry);
40/// assert_eq!(error.kind(), ErrorKind::RuntimeState);
41/// assert!(std::error::Error::source(&error).is_some());
42/// ```
43#[derive(Debug, thiserror::Error)]
44#[non_exhaustive]
45pub enum SessionEntryError {
46 /// An execution for this backend is already active on the calling thread
47 /// (or its managed worker scope), so opening another session would nest
48 /// provider or resource exclusion.
49 #[error(
50 "{backend}: session entry rejected because an execution is already active on this \
51 thread; pass the entered session to nested operations instead of entering the \
52 backend again"
53 )]
54 Reentered {
55 /// Backend that rejected the entry.
56 backend: &'static str,
57 },
58 /// A resource that admission cannot wait for is held by another user, such
59 /// as a caller-managed CPU domain already executing elsewhere.
60 #[error("{backend}: session entry rejected because the execution resource is busy: {message}")]
61 Contended {
62 /// Backend that rejected the entry.
63 backend: &'static str,
64 /// Which resource was busy.
65 message: String,
66 },
67 /// The session cannot be opened in the declared or active execution
68 /// context, for example an execution scope entered for a different backend.
69 #[error("{backend}: session entry rejected: {message}")]
70 IncompatibleContext {
71 /// Backend that rejected the entry.
72 backend: &'static str,
73 /// Which context requirement failed.
74 message: String,
75 },
76 /// Admission state was poisoned by an earlier panic and cannot be trusted.
77 #[error("{backend}: session entry rejected because {resource} is poisoned")]
78 ResourcePoisoned {
79 /// Backend that rejected the entry.
80 backend: &'static str,
81 /// Poisoned admission resource.
82 resource: &'static str,
83 },
84 /// The backend's executor could not be entered.
85 #[error("{backend}: executor entry failed: {source}")]
86 Executor {
87 /// Backend that rejected the entry.
88 backend: &'static str,
89 /// Typed executor failure.
90 #[source]
91 source: BoxError,
92 },
93}
94
95impl SessionEntryError {
96 /// Return the backend that rejected the session entry.
97 ///
98 /// # Examples
99 ///
100 /// ```rust
101 /// use tenferro_tensor::SessionEntryError;
102 ///
103 /// let error = SessionEntryError::ResourcePoisoned {
104 /// backend: "CpuBackend",
105 /// resource: "the CPU resource arbiter",
106 /// };
107 /// assert_eq!(error.backend(), "CpuBackend");
108 /// ```
109 #[must_use]
110 pub fn backend(&self) -> &'static str {
111 match self {
112 Self::Reentered { backend }
113 | Self::Contended { backend, .. }
114 | Self::IncompatibleContext { backend, .. }
115 | Self::ResourcePoisoned { backend, .. }
116 | Self::Executor { backend, .. } => backend,
117 }
118 }
119
120 /// Return the coarse classification of this failure.
121 ///
122 /// Every session-entry failure is invalid or unavailable execution state.
123 ///
124 /// # Examples
125 ///
126 /// ```rust
127 /// use tenferro_tensor::{ErrorKind, SessionEntryError};
128 ///
129 /// let error = SessionEntryError::Contended {
130 /// backend: "CpuBackend",
131 /// message: "caller-managed CPU domain".into(),
132 /// };
133 /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
134 /// ```
135 #[must_use]
136 pub fn kind(&self) -> ErrorKind {
137 ErrorKind::RuntimeState
138 }
139}
140
141#[cfg(test)]
142#[path = "session_entry/tests.rs"]
143mod tests;