Skip to main content

tenferro_runtime/runtime/
event_domain.rs

1use std::any::Any;
2use std::fmt;
3use std::sync::Arc;
4
5use super::EventDomainId;
6
7/// Classifies the runtime boundary at which event-domain provenance is
8/// validated.
9///
10/// The value is semantic control data for EventDomainError; provider
11/// function names are deliberately not part of this contract.
12///
13/// # Examples
14///
15/// ```
16/// use tenferro_runtime::EventDomainOperation;
17///
18/// assert_eq!(EventDomainOperation::Enqueue, EventDomainOperation::Enqueue);
19/// ```
20#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
21pub enum EventDomainOperation {
22    /// A driver is beginning a run for a frozen domain.
23    BeginRun,
24    /// A run is admitting dependencies and a launch.
25    Enqueue,
26    /// A run is being retired after all submitted work.
27    Drain,
28    /// The scheduler is host-bridging a source completion for a transfer.
29    TransferBridge,
30    /// The scheduler is validating a returned completion token.
31    ValidateCompletion,
32}
33
34impl fmt::Display for EventDomainOperation {
35    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36        let name = match self {
37            Self::BeginRun => "begin run",
38            Self::Enqueue => "enqueue",
39            Self::Drain => "drain",
40            Self::TransferBridge => "transfer bridge",
41            Self::ValidateCompletion => "validate completion",
42        };
43        formatter.write_str(name)
44    }
45}
46
47/// Structured event-domain admission and provenance failure.
48///
49/// Every provenance mismatch retains the expected and actual domains together
50/// with the operation and optional scheduled-node context; a missing-driver
51/// failure identifies the unavailable domain directly. Providers use the same
52/// vocabulary for direct admission failures.
53///
54/// # Examples
55///
56/// ```
57/// use tenferro_runtime::runtime::EventDomainError;
58///
59/// fn inspect(error: &EventDomainError) {
60///     let _ = error;
61/// }
62/// ```
63#[derive(Debug, thiserror::Error)]
64#[non_exhaustive]
65pub enum EventDomainError {
66    /// The scheduler reached a domain for which no driver run was registered.
67    #[error("event domain {domain:?} has no registered driver")]
68    MissingDriver {
69        /// Event domain whose driver run was unavailable.
70        domain: EventDomainId,
71    },
72    /// A driver returned a run for a domain other than the requested domain.
73    #[error(
74        "{operation} node {node_index:?} returned run domain {actual:?}, expected {expected:?}"
75    )]
76    RunDomainMismatch {
77        /// Operation validating the run.
78        operation: EventDomainOperation,
79        /// Scheduled node, when the validation belongs to a node.
80        node_index: Option<usize>,
81        /// Domain requested by the runtime.
82        expected: EventDomainId,
83        /// Domain reported by the driver run.
84        actual: EventDomainId,
85    },
86    /// A dependency token was not from the domain admitted at this boundary.
87    #[error(
88        "{operation} node {node_index:?} received dependency origin {actual:?}, expected {expected:?}"
89    )]
90    DependencyDomainMismatch {
91        /// Operation validating the dependency.
92        operation: EventDomainOperation,
93        /// Scheduled node, when the validation belongs to a node.
94        node_index: Option<usize>,
95        /// Domain that may admit the dependency.
96        expected: EventDomainId,
97        /// Origin reported by the dependency token.
98        actual: EventDomainId,
99    },
100    /// A driver returned a completion token with the wrong origin.
101    #[error(
102        "{operation} node {node_index:?} returned completion origin {actual:?}, expected {expected:?}"
103    )]
104    CompletionTokenDomainMismatch {
105        /// Operation validating the completion.
106        operation: EventDomainOperation,
107        /// Scheduled node, when the validation belongs to a node.
108        node_index: Option<usize>,
109        /// Completion domain assigned by the schedule.
110        expected: EventDomainId,
111        /// Origin reported by the returned token.
112        actual: EventDomainId,
113    },
114    /// A public event-domain run panicked while being explicitly retired.
115    #[error("{operation} for event domain {domain:?} panicked: {message}")]
116    DrainPanicked {
117        /// Operation that contained the provider panic.
118        operation: EventDomainOperation,
119        /// Domain whose run was being retired.
120        domain: EventDomainId,
121        /// Safe diagnostic text extracted from the panic payload.
122        message: String,
123    },
124    /// A token has the right origin but cannot be admitted by this provider.
125    #[error(
126        "{operation} node {node_index:?} cannot admit token type {token_type} for event domain {actual:?} (expected {expected:?})"
127    )]
128    IncompatibleTokenType {
129        /// Operation validating the token.
130        operation: EventDomainOperation,
131        /// Scheduled node, when the validation belongs to a node.
132        node_index: Option<usize>,
133        /// Domain the run admits.
134        expected: EventDomainId,
135        /// Origin reported by the token.
136        actual: EventDomainId,
137        /// Provider-neutral diagnostic name of the token type.
138        token_type: &'static str,
139    },
140    /// The scheduler's host bridge could not wait for a source completion.
141    #[error(
142        "{operation} node {node_index:?} failed waiting for dependency origin {actual:?} before destination domain {expected:?}: {source}"
143    )]
144    DependencyWaitFailed {
145        /// Operation validating the transfer bridge.
146        operation: EventDomainOperation,
147        /// Scheduled node containing the failed bridge.
148        node_index: Option<usize>,
149        /// Destination domain that was not launched.
150        expected: EventDomainId,
151        /// Origin domain whose token failed to wait.
152        actual: EventDomainId,
153        /// Original host-wait failure.
154        #[source]
155        source: crate::error::BoxError,
156    },
157}
158
159/// Opaque completion token produced by one event-domain run.
160///
161/// `wait` is repeatable and safe to call concurrently. A schedule may fan one
162/// completion out to multiple foreign event domains, so implementations must
163/// not consume the token on the first wait.
164///
165/// # Examples
166///
167/// ```
168/// use tenferro_runtime::runtime::EventToken;
169///
170/// fn inspect(token: &dyn EventToken) -> tenferro_runtime::Result<()> {
171///     let _origin = token.origin();
172///     let _opaque = token.as_any();
173///     token.wait()?;
174///     token.wait()
175/// }
176/// ```
177pub trait EventToken: fmt::Debug + Send + Sync + 'static {
178    /// Return the token as [`Any`] for driver-specific inspection.
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// use tenferro_runtime::{EventDomainDriver, EventDomainId};
184    ///
185    /// fn inspect(
186    ///     driver: &dyn EventDomainDriver,
187    ///     domain: EventDomainId,
188    /// ) -> tenferro_runtime::Result<()> {
189    ///     let mut run = driver.begin_run(domain)?;
190    ///     let mut launch = || Ok(());
191    ///     let token = run.enqueue(&[], &mut launch)?;
192    ///     let _opaque = token.as_any();
193    ///     Ok(())
194    /// }
195    /// ```
196    fn as_any(&self) -> &dyn Any;
197
198    /// Return the exact frozen event domain that produced this token.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use tenferro_runtime::{EventDomainId, EventToken};
204    ///
205    /// fn inspect(token: &dyn EventToken, expected: EventDomainId) {
206    ///     assert_eq!(token.origin(), expected);
207    /// }
208    /// ```
209    fn origin(&self) -> EventDomainId;
210
211    /// Wait on the host until this completion becomes ready.
212    ///
213    /// The runtime scheduler uses this repeatable host bridge only after it
214    /// classifies a scheduled cross-domain transfer dependency. Destination
215    /// drivers must not use it as a generic foreign-token fallback.
216    ///
217    /// # Errors
218    ///
219    /// Returns a typed backend or runtime error when the native completion
220    /// reports failure.
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// use tenferro_runtime::EventToken;
226    ///
227    /// fn wait_repeatably(token: &dyn EventToken) -> tenferro_runtime::Result<()> {
228    ///     token.wait()?;
229    ///     token.wait()
230    /// }
231    /// ```
232    fn wait(&self) -> crate::Result<()>;
233}
234
235/// Per-execution state owned by one event domain.
236///
237/// # Examples
238///
239/// ```
240/// use tenferro_runtime::{EventDomainId, EventDomainRun};
241///
242/// fn submit(
243///     run: &mut dyn EventDomainRun,
244///     domain: EventDomainId,
245/// ) -> tenferro_runtime::Result<()> {
246///     assert_eq!(run.domain(), domain);
247///     let mut launch = || Ok(());
248///     run.enqueue(&[], &mut launch)?;
249///     run.drain()
250/// }
251/// ```
252pub trait EventDomainRun: fmt::Debug + Send {
253    /// Return the exact domain admitted by this run.
254    ///
255    /// # Examples
256    ///
257    /// ```
258    /// use tenferro_runtime::{EventDomainId, EventDomainRun};
259    ///
260    /// fn inspect(run: &dyn EventDomainRun, expected: EventDomainId) {
261    ///     assert_eq!(run.domain(), expected);
262    /// }
263    /// ```
264    fn domain(&self) -> EventDomainId;
265
266    /// Enqueue one launch after its dependency tokens.
267    ///
268    /// # Errors
269    ///
270    /// Returns a typed runtime error when a dependency token is incompatible
271    /// with this domain, native queue submission fails, or `launch` returns an
272    /// error.
273    ///
274    /// The implementation must not invoke `launch` when dependency admission
275    /// or waiting fails. After dependencies are admitted, it invokes `launch`
276    /// exactly once without holding a driver lock. The closure submits work to
277    /// the native queue; it does not wait for that work to complete.
278    ///
279    /// # Examples
280    ///
281    /// ```
282    /// use tenferro_runtime::EventDomainRun;
283    ///
284    /// fn submit(run: &mut dyn EventDomainRun) -> tenferro_runtime::Result<()> {
285    ///     let mut launched = false;
286    ///     let mut launch = || {
287    ///         launched = true;
288    ///         Ok(())
289    ///     };
290    ///     run.enqueue(&[], &mut launch)?;
291    ///     assert!(launched);
292    ///     Ok(())
293    /// }
294    /// ```
295    fn enqueue(
296        &mut self,
297        dependencies: &[Arc<dyn EventToken>],
298        launch: &mut dyn FnMut() -> crate::Result<()>,
299    ) -> crate::Result<Arc<dyn EventToken>>;
300
301    /// Wait for all work already enqueued in this run.
302    ///
303    /// # Errors
304    ///
305    /// Returns the first driver-defined typed error, such as
306    /// [`crate::Error::Internal`], when a queued completion reports failure or
307    /// the native queue cannot be synchronized.
308    ///
309    /// Success and error are both retirement boundaries: before returning, the
310    /// run must ensure that no previously enqueued work can access tensors,
311    /// tokens, or native resources retained by the caller. An error reports the
312    /// completion failure; it must not mean that work is still using those
313    /// resources. Implementations must not unwind from this method.
314    ///
315    /// Draining observes already-progressing work. It must not require another
316    /// event domain's drain call to start that work.
317    ///
318    /// Dropping a run must perform equivalent best-effort retirement when
319    /// explicit draining is skipped by panic unwinding. Neither explicit
320    /// draining nor implicit retirement may unwind into the caller.
321    fn drain(&mut self) -> crate::Result<()>;
322}
323
324/// Runtime-owned factory for per-execution event-domain state.
325///
326/// # Examples
327///
328/// ```
329/// use tenferro_runtime::{EventDomainDriver, EventDomainId};
330///
331/// fn begin(
332///     driver: &dyn EventDomainDriver,
333///     domain: EventDomainId,
334/// ) -> tenferro_runtime::Result<()> {
335///     let mut run = driver.begin_run(domain)?;
336///     run.drain()
337/// }
338/// ```
339pub trait EventDomainDriver: fmt::Debug + Send + Sync + 'static {
340    /// Begin one isolated execution run for exactly `domain`.
341    ///
342    /// # Errors
343    ///
344    /// Returns a driver-defined typed error, such as [`crate::Error::Internal`],
345    /// when native queue, stream, or per-run event-state allocation fails, or
346    /// when the domain cannot admit a new run.
347    fn begin_run(&self, domain: EventDomainId) -> crate::Result<Box<dyn EventDomainRun>>;
348}
349
350/// Completion token returned by the blocking immediate domain.
351#[derive(Debug)]
352struct ReadyEventToken {
353    origin: EventDomainId,
354}
355
356impl EventToken for ReadyEventToken {
357    fn as_any(&self) -> &dyn Any {
358        self
359    }
360
361    fn origin(&self) -> EventDomainId {
362        self.origin
363    }
364
365    fn wait(&self) -> crate::Result<()> {
366        Ok(())
367    }
368}
369
370/// Blocking event-domain driver used by synchronous CPU engines.
371///
372/// # Examples
373///
374/// ```
375/// use tenferro_runtime::runtime::ImmediateEventDomainDriver;
376///
377/// let _driver = ImmediateEventDomainDriver::new();
378/// ```
379#[derive(Clone, Copy, Debug, Default)]
380pub struct ImmediateEventDomainDriver;
381
382impl ImmediateEventDomainDriver {
383    /// Construct a blocking immediate driver.
384    ///
385    /// # Examples
386    ///
387    /// ```
388    /// use tenferro_runtime::runtime::ImmediateEventDomainDriver;
389    ///
390    /// let _driver = ImmediateEventDomainDriver::new();
391    /// ```
392    #[must_use]
393    pub const fn new() -> Self {
394        Self
395    }
396}
397
398impl EventDomainDriver for ImmediateEventDomainDriver {
399    fn begin_run(&self, domain: EventDomainId) -> crate::Result<Box<dyn EventDomainRun>> {
400        Ok(Box::new(ImmediateEventDomainRun { domain }))
401    }
402}
403
404#[derive(Debug)]
405struct ImmediateEventDomainRun {
406    domain: EventDomainId,
407}
408
409impl EventDomainRun for ImmediateEventDomainRun {
410    fn domain(&self) -> EventDomainId {
411        self.domain
412    }
413
414    fn enqueue(
415        &mut self,
416        dependencies: &[Arc<dyn EventToken>],
417        launch: &mut dyn FnMut() -> crate::Result<()>,
418    ) -> crate::Result<Arc<dyn EventToken>> {
419        for dependency in dependencies {
420            let actual = dependency.origin();
421            if actual != self.domain {
422                return Err(crate::Error::from(
423                    EventDomainError::DependencyDomainMismatch {
424                        operation: EventDomainOperation::Enqueue,
425                        node_index: None,
426                        expected: self.domain,
427                        actual,
428                    },
429                ));
430            }
431            dependency.wait()?;
432        }
433        launch()?;
434        Ok(Arc::new(ReadyEventToken {
435            origin: self.domain,
436        }))
437    }
438
439    fn drain(&mut self) -> crate::Result<()> {
440        Ok(())
441    }
442}
443
444#[cfg(test)]
445mod tests;