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    /// Accept the single-domain coalesced submission fast path when supported.
267    ///
268    /// The scheduler calls this at most once per run, before any launch, and
269    /// only after it has proven that every scheduled location and node of the
270    /// run uses this exact domain and that the schedule contains no transfer
271    /// node. Returning `true` promises that same-stream submission order alone
272    /// orders this run's nodes: the scheduler then submits each launch through
273    /// this run without dependency tokens and records no per-node completion,
274    /// keeping only the run's own [`Self::drain`] retirement.
275    ///
276    /// Returning `false` (the default) keeps the per-node completion contract,
277    /// so an unknown provider behaves exactly as before. A run must not return
278    /// `true` unless it really keeps only submission order for ordering, because
279    /// a node would otherwise be allowed to run before the node it depends on.
280    /// Operation-level barriers that a launch needs for raw or vendor work are
281    /// unaffected.
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use tenferro_runtime::EventDomainRun;
287    ///
288    /// fn keeps_per_node_completions(run: &mut dyn EventDomainRun) -> bool {
289    ///     !run.enable_single_domain_coalescing()
290    /// }
291    /// ```
292    fn enable_single_domain_coalescing(&mut self) -> bool {
293        false
294    }
295
296    /// Enqueue one launch after its dependency tokens.
297    ///
298    /// # Errors
299    ///
300    /// Returns a typed runtime error when a dependency token is incompatible
301    /// with this domain, native queue submission fails, or `launch` returns an
302    /// error.
303    ///
304    /// The implementation must not invoke `launch` when dependency admission
305    /// or waiting fails. After dependencies are admitted, it invokes `launch`
306    /// exactly once without holding a driver lock. The closure submits work to
307    /// the native queue; it does not wait for that work to complete.
308    ///
309    /// # Examples
310    ///
311    /// ```
312    /// use tenferro_runtime::EventDomainRun;
313    ///
314    /// fn submit(run: &mut dyn EventDomainRun) -> tenferro_runtime::Result<()> {
315    ///     let mut launched = false;
316    ///     let mut launch = || {
317    ///         launched = true;
318    ///         Ok(())
319    ///     };
320    ///     run.enqueue(&[], &mut launch)?;
321    ///     assert!(launched);
322    ///     Ok(())
323    /// }
324    /// ```
325    fn enqueue(
326        &mut self,
327        dependencies: &[Arc<dyn EventToken>],
328        launch: &mut dyn FnMut() -> crate::Result<()>,
329    ) -> crate::Result<Arc<dyn EventToken>>;
330
331    /// Wait for all work already enqueued in this run.
332    ///
333    /// # Errors
334    ///
335    /// Returns the first driver-defined typed error, such as
336    /// [`crate::Error::Internal`], when a queued completion reports failure or
337    /// the native queue cannot be synchronized.
338    ///
339    /// Success and error are both retirement boundaries: before returning, the
340    /// run must ensure that no previously enqueued work can access tensors,
341    /// tokens, or native resources retained by the caller. An error reports the
342    /// completion failure; it must not mean that work is still using those
343    /// resources. Implementations must not unwind from this method.
344    ///
345    /// Draining observes already-progressing work. It must not require another
346    /// event domain's drain call to start that work.
347    ///
348    /// Dropping a run must perform equivalent best-effort retirement when
349    /// explicit draining is skipped by panic unwinding. Neither explicit
350    /// draining nor implicit retirement may unwind into the caller.
351    fn drain(&mut self) -> crate::Result<()>;
352}
353
354/// Runtime-owned factory for per-execution event-domain state.
355///
356/// # Examples
357///
358/// ```
359/// use tenferro_runtime::{EventDomainDriver, EventDomainId};
360///
361/// fn begin(
362///     driver: &dyn EventDomainDriver,
363///     domain: EventDomainId,
364/// ) -> tenferro_runtime::Result<()> {
365///     let mut run = driver.begin_run(domain)?;
366///     run.drain()
367/// }
368/// ```
369pub trait EventDomainDriver: fmt::Debug + Send + Sync + 'static {
370    /// Report whether this provider proves retirement before a scoped call returns.
371    ///
372    /// The default is conservative: asynchronous queues and providers with
373    /// unknown retirement semantics cannot admit borrowed scoped inputs.
374    fn supports_scoped_read_only(&self) -> bool {
375        false
376    }
377
378    /// Begin one isolated execution run for exactly `domain`.
379    ///
380    /// # Errors
381    ///
382    /// Returns a driver-defined typed error, such as [`crate::Error::Internal`],
383    /// when native queue, stream, or per-run event-state allocation fails, or
384    /// when the domain cannot admit a new run.
385    fn begin_run(&self, domain: EventDomainId) -> crate::Result<Box<dyn EventDomainRun>>;
386}
387
388/// Completion token returned by the blocking immediate domain.
389#[derive(Debug)]
390struct ReadyEventToken {
391    origin: EventDomainId,
392}
393
394impl EventToken for ReadyEventToken {
395    fn as_any(&self) -> &dyn Any {
396        self
397    }
398
399    fn origin(&self) -> EventDomainId {
400        self.origin
401    }
402
403    fn wait(&self) -> crate::Result<()> {
404        Ok(())
405    }
406}
407
408/// Blocking event-domain driver used by synchronous CPU engines.
409///
410/// # Examples
411///
412/// ```
413/// use tenferro_runtime::runtime::ImmediateEventDomainDriver;
414///
415/// let _driver = ImmediateEventDomainDriver::new();
416/// ```
417#[derive(Clone, Copy, Debug, Default)]
418pub struct ImmediateEventDomainDriver;
419
420impl ImmediateEventDomainDriver {
421    /// Construct a blocking immediate driver.
422    ///
423    /// # Examples
424    ///
425    /// ```
426    /// use tenferro_runtime::runtime::ImmediateEventDomainDriver;
427    ///
428    /// let _driver = ImmediateEventDomainDriver::new();
429    /// ```
430    #[must_use]
431    pub const fn new() -> Self {
432        Self
433    }
434}
435
436impl EventDomainDriver for ImmediateEventDomainDriver {
437    fn supports_scoped_read_only(&self) -> bool {
438        true
439    }
440
441    fn begin_run(&self, domain: EventDomainId) -> crate::Result<Box<dyn EventDomainRun>> {
442        Ok(Box::new(ImmediateEventDomainRun {
443            domain,
444            coalesced: false,
445            shared_token: None,
446        }))
447    }
448}
449
450#[derive(Debug)]
451struct ImmediateEventDomainRun {
452    domain: EventDomainId,
453    /// Set when the scheduler proved that this run is the only domain and has
454    /// no transfer node. Every launch retires before `enqueue` returns here, so
455    /// submission order alone already orders the run's nodes and the per-node
456    /// completion tokens carry no information.
457    coalesced: bool,
458    /// The token every coalesced completion reports, created on first use.
459    ///
460    /// A coalesced run's token only carries the domain, so one shared token
461    /// answers every node instead of one allocation per node.
462    shared_token: Option<Arc<dyn EventToken>>,
463}
464
465impl EventDomainRun for ImmediateEventDomainRun {
466    fn domain(&self) -> EventDomainId {
467        self.domain
468    }
469
470    fn enable_single_domain_coalescing(&mut self) -> bool {
471        self.coalesced = true;
472        true
473    }
474
475    fn enqueue(
476        &mut self,
477        dependencies: &[Arc<dyn EventToken>],
478        launch: &mut dyn FnMut() -> crate::Result<()>,
479    ) -> crate::Result<Arc<dyn EventToken>> {
480        for dependency in dependencies {
481            let actual = dependency.origin();
482            if actual != self.domain {
483                return Err(crate::Error::from(
484                    EventDomainError::DependencyDomainMismatch {
485                        operation: EventDomainOperation::Enqueue,
486                        node_index: None,
487                        expected: self.domain,
488                        actual,
489                    },
490                ));
491            }
492            dependency.wait()?;
493        }
494        launch()?;
495        if self.coalesced {
496            let token = self.shared_token.get_or_insert_with(|| {
497                Arc::new(ReadyEventToken {
498                    origin: self.domain,
499                }) as Arc<dyn EventToken>
500            });
501            return Ok(Arc::clone(token));
502        }
503        Ok(Arc::new(ReadyEventToken {
504            origin: self.domain,
505        }))
506    }
507
508    fn drain(&mut self) -> crate::Result<()> {
509        Ok(())
510    }
511}
512
513#[cfg(test)]
514mod tests;