Skip to main content

dataflow_rs/run/
options.rs

1//! The options of one run.
2
3use super::budget::{ConditionErrorPolicy, RunAccountant};
4use super::message::AuditMode;
5use crate::base::{CancelToken, Instant};
6use crate::function::CallLayer;
7use crate::observe::{Observer, TraceOptions};
8use std::fmt;
9use std::sync::Arc;
10
11/// What a caller chooses about one run.
12#[derive(Clone)]
13#[non_exhaustive]
14pub struct RunOptions<'a> {
15    /// Run the Active workflows of this channel; `None` runs every Active one.
16    pub channel: Option<&'a str>,
17    /// The bucket a rollout admits, `0..=99`; a larger one is served as 99.
18    /// `None` is admitted by every rollout.
19    pub routing_bucket: Option<u8>,
20    /// Record each write's `Change` in its task's audit entry. `true` by
21    /// default (#63). It governs only the audit trail: a trace records its
22    /// own changes whatever it says.
23    pub capture_changes: bool,
24    /// Which audit entries the message keeps. `Full` by default.
25    pub audit: AuditMode,
26    /// When the run must end; `None` by default. It is checked at every
27    /// host call, every fan-out element's start, every sweep and every
28    /// [`CHECK_EVERY`](crate::run::CHECK_EVERY) ops. Natively each host call also races it, on a Tokio timer, so a
29    /// run with a deadline is polled inside a Tokio runtime with its time
30    /// driver enabled. A call it cuts short, or that returns `Err` once it
31    /// has passed, is recorded as `TIMEOUT_ERROR` on its task; either way
32    /// the run ends [`TimedOut`].
33    ///
34    /// On wasm no timer exists, so nothing races a host call and it is
35    /// never cut short mid-flight: a deadline that passes while a call runs
36    /// is seen at the next check, after the call returns.
37    ///
38    /// [`TimedOut`]: crate::run::RunStatus::TimedOut
39    pub deadline: Option<Instant>,
40    /// A token that cancels the run when it, or an ancestor, is cancelled;
41    /// `None` by default. It is checked and raced where the deadline is,
42    /// and first. A call it cuts short, or that returns `Err` once it is
43    /// cancelled, is recorded as `CANCELLED` on its task; either way the
44    /// run ends [`Cancelled`].
45    ///
46    /// [`Cancelled`]: crate::run::RunStatus::Cancelled
47    pub cancel: Option<&'a CancelToken>,
48    /// Layers around every host call of this run, outside the registry's;
49    /// the first is the outermost. None by default.
50    pub layers: &'a [Arc<dyn CallLayer>],
51    /// An observer of this run, beside the engine's
52    /// ([`EngineConfig::observer`]); `None` by default.
53    ///
54    /// [`EngineConfig::observer`]: crate::run::EngineConfig::observer()
55    pub observer: Option<&'a dyn Observer>,
56    /// Trace the run, as these options say: its [`Run::trace`] is then the
57    /// initial context plus each step's changes. `None` by default: no
58    /// trace, and no copy of the context.
59    ///
60    /// [`Run::trace`]: crate::Run::trace()
61    pub trace: Option<TraceOptions>,
62    /// How condition evaluation errors are handled during this run; `None`
63    /// inherits the engine's [`EngineConfig::condition_error_policy`](super::EngineConfig::condition_error_policy)
64    /// (which defaults to [`ConditionErrorPolicy::Skip`]).
65    pub condition_error_policy: Option<ConditionErrorPolicy>,
66    /// Maximum number of task dispatches (inline built-ins, host calls, loop
67    /// sweeps, and `for_each` elements) permitted during this run; `None` by
68    /// default.
69    pub task_budget: Option<u64>,
70    /// Maximum cumulative datalogic operations permitted across all
71    /// expressions evaluated during this run; `None` by default.
72    pub expr_ops_budget: Option<u64>,
73    /// Host-owned accounting and admission hook for this run; `None` by
74    /// default.
75    pub accountant: Option<&'a dyn RunAccountant>,
76}
77
78impl RunOptions<'static> {
79    /// The defaults, as a constant: `Engine::run` borrows a static copy.
80    pub(crate) const DEFAULT: Self = Self {
81        channel: None,
82        routing_bucket: None,
83        capture_changes: true,
84        audit: AuditMode::Full,
85        deadline: None,
86        cancel: None,
87        layers: &[],
88        observer: None,
89        trace: None,
90        condition_error_policy: None,
91        task_budget: None,
92        expr_ops_budget: None,
93        accountant: None,
94    };
95}
96
97impl Default for RunOptions<'_> {
98    fn default() -> Self {
99        RunOptions::DEFAULT
100    }
101}
102
103impl fmt::Debug for RunOptions<'_> {
104    /// Every option, the layers as a count and the observer as a presence.
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        let observer = if self.observer.is_some() {
107            "present"
108        } else {
109            "absent"
110        };
111        let mut s = f.debug_struct("RunOptions");
112        s.field("channel", &self.channel)
113            .field("routing_bucket", &self.routing_bucket)
114            .field("capture_changes", &self.capture_changes)
115            .field("audit", &self.audit)
116            .field("deadline", &self.deadline)
117            .field("cancel", &self.cancel)
118            .field("layers", &self.layers.len())
119            .field("observer", &format_args!("{observer}"))
120            .field("trace", &self.trace);
121        if let Some(policy) = &self.condition_error_policy {
122            s.field("condition_error_policy", policy);
123        }
124        if let Some(budget) = &self.task_budget {
125            s.field("task_budget", budget);
126        }
127        if let Some(budget) = &self.expr_ops_budget {
128            s.field("expr_ops_budget", budget);
129        }
130        if self.accountant.is_some() {
131            s.field("accountant", &format_args!("present"));
132        }
133        s.finish()
134    }
135}
136
137impl<'a> RunOptions<'a> {
138    /// Runs the Active workflows of channel `c`.
139    #[must_use]
140    pub fn channel(mut self, c: &'a str) -> Self {
141        self.channel = Some(c);
142        self
143    }
144
145    /// Sets the routing bucket rollouts are tested against: `0..=99`. A
146    /// larger one is served as bucket 99, as in 3.x, so derive it as a
147    /// hash `% 100`, never `% 256`.
148    #[must_use]
149    pub fn routing_bucket(mut self, b: u8) -> Self {
150        self.routing_bucket = Some(b);
151        self
152    }
153
154    /// Turns the audit trail's change capture on or off.
155    #[must_use]
156    pub fn capture_changes(mut self, on: bool) -> Self {
157        self.capture_changes = on;
158        self
159    }
160
161    /// Sets which audit entries the message keeps.
162    #[must_use]
163    pub fn audit(mut self, mode: AuditMode) -> Self {
164        self.audit = mode;
165        self
166    }
167
168    /// Ends the run at `deadline`, as the `deadline` field says.
169    #[must_use]
170    pub fn deadline(mut self, deadline: Instant) -> Self {
171        self.deadline = Some(deadline);
172        self
173    }
174
175    /// Cancels the run when `token` is cancelled, as the `cancel` field
176    /// says.
177    #[must_use]
178    pub fn cancel(mut self, token: &'a CancelToken) -> Self {
179        self.cancel = Some(token);
180        self
181    }
182
183    /// Wraps every host call of the run in `layers`, outside the registry's
184    /// layers; the first is the outermost.
185    #[must_use]
186    pub fn layers(mut self, layers: &'a [Arc<dyn CallLayer>]) -> Self {
187        self.layers = layers;
188        self
189    }
190
191    /// Reports this run to `o` too, beside the engine's observer.
192    #[must_use]
193    pub fn observer(mut self, o: &'a dyn Observer) -> Self {
194        self.observer = Some(o);
195        self
196    }
197
198    /// Traces the run under `opts`, as the `trace` field says.
199    #[must_use]
200    pub fn trace(mut self, opts: TraceOptions) -> Self {
201        self.trace = Some(opts);
202        self
203    }
204
205    /// Sets how condition evaluation errors are handled during this run.
206    #[must_use]
207    pub fn condition_error_policy(mut self, policy: ConditionErrorPolicy) -> Self {
208        self.condition_error_policy = Some(policy);
209        self
210    }
211
212    /// Alias for [`Self::condition_error_policy`].
213    #[must_use]
214    pub fn with_condition_error_policy(self, policy: ConditionErrorPolicy) -> Self {
215        self.condition_error_policy(policy)
216    }
217
218    /// Sets the maximum number of task dispatches permitted during this run.
219    #[must_use]
220    pub fn task_budget(mut self, max_tasks: u64) -> Self {
221        self.task_budget = Some(max_tasks);
222        self
223    }
224
225    /// Alias for [`Self::task_budget`].
226    #[must_use]
227    pub fn with_task_budget(self, max_tasks: u64) -> Self {
228        self.task_budget(max_tasks)
229    }
230
231    /// Sets the maximum cumulative datalogic operations permitted across all
232    /// expressions evaluated during this run.
233    #[must_use]
234    pub fn expr_ops_budget(mut self, max_ops: u64) -> Self {
235        self.expr_ops_budget = Some(max_ops);
236        self
237    }
238
239    /// Alias for [`Self::expr_ops_budget`].
240    #[must_use]
241    pub fn with_expr_ops_budget(self, max_ops: u64) -> Self {
242        self.expr_ops_budget(max_ops)
243    }
244
245    /// Attaches a host-owned [`RunAccountant`] to this run.
246    #[must_use]
247    pub fn accountant(mut self, accountant: &'a dyn RunAccountant) -> Self {
248        self.accountant = Some(accountant);
249        self
250    }
251
252    /// Alias for [`Self::accountant`].
253    #[must_use]
254    pub fn with_accountant(self, accountant: &'a dyn RunAccountant) -> Self {
255        self.accountant(accountant)
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn run_options_default_captures_changes_and_keeps_the_full_audit() {
265        let o = RunOptions::default();
266        assert!(o.capture_changes);
267        assert_eq!(o.audit, AuditMode::Full);
268        assert_eq!(o.channel, None);
269        assert_eq!(o.routing_bucket, None);
270        assert!(o.deadline.is_none() && o.cancel.is_none());
271        assert!(o.layers.is_empty());
272        assert!(o.observer.is_none());
273        assert!(o.trace.is_none());
274        let layers: Vec<Arc<dyn CallLayer>> = vec![Arc::new(Pass)];
275        let token = CancelToken::new();
276        let o = o
277            .channel("c")
278            .routing_bucket(7)
279            .capture_changes(false)
280            .audit(AuditMode::Off)
281            .cancel(&token)
282            .layers(&layers);
283        assert_eq!((o.channel, o.routing_bucket), (Some("c"), Some(7)));
284        assert!(!o.capture_changes);
285        assert_eq!(o.audit, AuditMode::Off);
286        assert_eq!(o.layers.len(), 1);
287        assert_eq!(
288            format!("{o:?}"),
289            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 }"#
290        );
291        let watcher = Quiet;
292        let o = o.observer(&watcher);
293        assert!(format!("{o:?}").ends_with("layers: 1, observer: present, trace: None }"));
294        let o = o.trace(TraceOptions::default().timings(true));
295        assert!(o.trace.as_ref().is_some_and(|t| t.timings && t.changes));
296        let at = Instant::now();
297        assert_eq!(o.deadline(at).deadline, Some(at));
298    }
299
300    /// An observer that keeps nothing.
301    struct Quiet;
302
303    impl Observer for Quiet {
304        fn on(&self, _event: &crate::observe::Event<'_>) {}
305    }
306
307    /// A layer that goes straight on.
308    struct Pass;
309
310    impl CallLayer for Pass {
311        fn call<'a>(
312            &'a self,
313            ctx: &'a mut crate::function::Ctx<'_>,
314            next: crate::function::Next<'a>,
315        ) -> crate::function::BoxFuture<'a, crate::base::Result<crate::function::Outcome>> {
316            next.run(ctx)
317        }
318    }
319}