Skip to main content

dataflow_rs/run/
options.rs

1//! The options of one run.
2
3use super::message::AuditMode;
4use crate::base::{CancelToken, Instant};
5use crate::function::CallLayer;
6use crate::observe::{Observer, TraceOptions};
7use std::fmt;
8use std::sync::Arc;
9
10/// What a caller chooses about one run.
11#[derive(Clone)]
12#[non_exhaustive]
13pub struct RunOptions<'a> {
14    /// Run the Active workflows of this channel; `None` runs every Active one.
15    pub channel: Option<&'a str>,
16    /// The bucket a rollout admits, `0..=99`; a larger one is served as 99.
17    /// `None` is admitted by every rollout.
18    pub routing_bucket: Option<u8>,
19    /// Record each write's `Change` in its task's audit entry. `true` by
20    /// default (#63). It governs only the audit trail: a trace records its
21    /// own changes whatever it says.
22    pub capture_changes: bool,
23    /// Which audit entries the message keeps. `Full` by default.
24    pub audit: AuditMode,
25    /// When the run must end; `None` by default. It is checked at every
26    /// host call, every fan-out element's start, every sweep and every
27    /// [`CHECK_EVERY`](crate::run::CHECK_EVERY) ops. Natively each host call also races it, on a Tokio timer, so a
28    /// run with a deadline is polled inside a Tokio runtime with its time
29    /// driver enabled. A call it cuts short, or that returns `Err` once it
30    /// has passed, is recorded as `TIMEOUT_ERROR` on its task; either way
31    /// the run ends [`TimedOut`].
32    ///
33    /// On wasm no timer exists, so nothing races a host call and it is
34    /// never cut short mid-flight: a deadline that passes while a call runs
35    /// is seen at the next check, after the call returns.
36    ///
37    /// [`TimedOut`]: crate::run::RunStatus::TimedOut
38    pub deadline: Option<Instant>,
39    /// A token that cancels the run when it, or an ancestor, is cancelled;
40    /// `None` by default. It is checked and raced where the deadline is,
41    /// and first. A call it cuts short, or that returns `Err` once it is
42    /// cancelled, is recorded as `CANCELLED` on its task; either way the
43    /// run ends [`Cancelled`].
44    ///
45    /// [`Cancelled`]: crate::run::RunStatus::Cancelled
46    pub cancel: Option<&'a CancelToken>,
47    /// Layers around every host call of this run, outside the registry's;
48    /// the first is the outermost. None by default.
49    pub layers: &'a [Arc<dyn CallLayer>],
50    /// An observer of this run, beside the engine's
51    /// ([`EngineConfig::observer`]); `None` by default.
52    ///
53    /// [`EngineConfig::observer`]: crate::run::EngineConfig::observer()
54    pub observer: Option<&'a dyn Observer>,
55    /// Trace the run, as these options say: its [`Run::trace`] is then the
56    /// initial context plus each step's changes. `None` by default: no
57    /// trace, and no copy of the context.
58    ///
59    /// [`Run::trace`]: crate::Run::trace()
60    pub trace: Option<TraceOptions>,
61}
62
63impl RunOptions<'static> {
64    /// The defaults, as a constant: `Engine::run` borrows a static copy.
65    pub(crate) const DEFAULT: Self = Self {
66        channel: None,
67        routing_bucket: None,
68        capture_changes: true,
69        audit: AuditMode::Full,
70        deadline: None,
71        cancel: None,
72        layers: &[],
73        observer: None,
74        trace: None,
75    };
76}
77
78impl Default for RunOptions<'_> {
79    fn default() -> Self {
80        RunOptions::DEFAULT
81    }
82}
83
84impl fmt::Debug for RunOptions<'_> {
85    /// Every option, the layers as a count and the observer as a presence.
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        let observer = if self.observer.is_some() {
88            "present"
89        } else {
90            "absent"
91        };
92        f.debug_struct("RunOptions")
93            .field("channel", &self.channel)
94            .field("routing_bucket", &self.routing_bucket)
95            .field("capture_changes", &self.capture_changes)
96            .field("audit", &self.audit)
97            .field("deadline", &self.deadline)
98            .field("cancel", &self.cancel)
99            .field("layers", &self.layers.len())
100            .field("observer", &format_args!("{observer}"))
101            .field("trace", &self.trace)
102            .finish()
103    }
104}
105
106impl<'a> RunOptions<'a> {
107    /// Runs the Active workflows of channel `c`.
108    #[must_use]
109    pub fn channel(mut self, c: &'a str) -> Self {
110        self.channel = Some(c);
111        self
112    }
113
114    /// Sets the routing bucket rollouts are tested against: `0..=99`. A
115    /// larger one is served as bucket 99, as in 3.x, so derive it as a
116    /// hash `% 100`, never `% 256`.
117    #[must_use]
118    pub fn routing_bucket(mut self, b: u8) -> Self {
119        self.routing_bucket = Some(b);
120        self
121    }
122
123    /// Turns the audit trail's change capture on or off.
124    #[must_use]
125    pub fn capture_changes(mut self, on: bool) -> Self {
126        self.capture_changes = on;
127        self
128    }
129
130    /// Sets which audit entries the message keeps.
131    #[must_use]
132    pub fn audit(mut self, mode: AuditMode) -> Self {
133        self.audit = mode;
134        self
135    }
136
137    /// Ends the run at `deadline`, as the `deadline` field says.
138    #[must_use]
139    pub fn deadline(mut self, deadline: Instant) -> Self {
140        self.deadline = Some(deadline);
141        self
142    }
143
144    /// Cancels the run when `token` is cancelled, as the `cancel` field
145    /// says.
146    #[must_use]
147    pub fn cancel(mut self, token: &'a CancelToken) -> Self {
148        self.cancel = Some(token);
149        self
150    }
151
152    /// Wraps every host call of the run in `layers`, outside the registry's
153    /// layers; the first is the outermost.
154    #[must_use]
155    pub fn layers(mut self, layers: &'a [Arc<dyn CallLayer>]) -> Self {
156        self.layers = layers;
157        self
158    }
159
160    /// Reports this run to `o` too, beside the engine's observer.
161    #[must_use]
162    pub fn observer(mut self, o: &'a dyn Observer) -> Self {
163        self.observer = Some(o);
164        self
165    }
166
167    /// Traces the run under `opts`, as the `trace` field says.
168    #[must_use]
169    pub fn trace(mut self, opts: TraceOptions) -> Self {
170        self.trace = Some(opts);
171        self
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn run_options_default_captures_changes_and_keeps_the_full_audit() {
181        let o = RunOptions::default();
182        assert!(o.capture_changes);
183        assert_eq!(o.audit, AuditMode::Full);
184        assert_eq!(o.channel, None);
185        assert_eq!(o.routing_bucket, None);
186        assert!(o.deadline.is_none() && o.cancel.is_none());
187        assert!(o.layers.is_empty());
188        assert!(o.observer.is_none());
189        assert!(o.trace.is_none());
190        let layers: Vec<Arc<dyn CallLayer>> = vec![Arc::new(Pass)];
191        let token = CancelToken::new();
192        let o = o
193            .channel("c")
194            .routing_bucket(7)
195            .capture_changes(false)
196            .audit(AuditMode::Off)
197            .cancel(&token)
198            .layers(&layers);
199        assert_eq!((o.channel, o.routing_bucket), (Some("c"), Some(7)));
200        assert!(!o.capture_changes);
201        assert_eq!(o.audit, AuditMode::Off);
202        assert_eq!(o.layers.len(), 1);
203        assert_eq!(
204            format!("{o:?}"),
205            r#"RunOptions { channel: Some("c"), routing_bucket: Some(7), capture_changes: false, audit: Off, deadline: None, cancel: Some(CancelToken { cancelled: false, .. }), layers: 1, observer: absent, trace: None }"#
206        );
207        let watcher = Quiet;
208        let o = o.observer(&watcher);
209        assert!(format!("{o:?}").ends_with("layers: 1, observer: present, trace: None }"));
210        let o = o.trace(TraceOptions::default().timings(true));
211        assert!(o.trace.as_ref().is_some_and(|t| t.timings && t.changes));
212        let at = Instant::now();
213        assert_eq!(o.deadline(at).deadline, Some(at));
214    }
215
216    /// An observer that keeps nothing.
217    struct Quiet;
218
219    impl Observer for Quiet {
220        fn on(&self, _event: &crate::observe::Event<'_>) {}
221    }
222
223    /// A layer that goes straight on.
224    struct Pass;
225
226    impl CallLayer for Pass {
227        fn call<'a>(
228            &'a self,
229            ctx: &'a mut crate::function::Ctx<'_>,
230            next: crate::function::Next<'a>,
231        ) -> crate::function::BoxFuture<'a, crate::base::Result<crate::function::Outcome>> {
232            next.run(ctx)
233        }
234    }
235}