Skip to main content

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;