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;