Skip to main content

a3s_effect/
effect.rs

1//! Programs as values.
2//!
3//! An [`Effect`] describes a program: the value it produces, the expected
4//! error it can return, and the services it needs. Constructing one does not
5//! run it. [`Effect::run`] is the edge where a description becomes a result.
6//!
7//! The operators follow the Effect v4 onboarding split: typed expected
8//! failures, retries as a schedule, structured concurrency that cancels the
9//! sibling it no longer needs, resource release on every exit including
10//! cancellation, services passed in the type, and spans recorded by the
11//! runtime.
12
13use std::future::Future;
14use std::marker::PhantomData;
15use std::pin::Pin;
16use std::sync::{Arc, Mutex};
17use std::time::Duration;
18
19use tokio_util::sync::CancellationToken;
20
21use crate::exit::Exit;
22
23pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct TraceEvent {
27    pub name: String,
28    pub outcome: &'static str,
29}
30
31pub struct RunCtx<S> {
32    pub services: Arc<S>,
33    pub cancel: CancellationToken,
34    trace: Arc<Mutex<Vec<TraceEvent>>>,
35}
36
37impl<S> Clone for RunCtx<S> {
38    fn clone(&self) -> Self {
39        Self {
40            services: Arc::clone(&self.services),
41            cancel: self.cancel.clone(),
42            trace: Arc::clone(&self.trace),
43        }
44    }
45}
46
47impl<S> RunCtx<S> {
48    pub fn child(&self) -> Self {
49        Self {
50            services: Arc::clone(&self.services),
51            cancel: self.cancel.child_token(),
52            trace: Arc::clone(&self.trace),
53        }
54    }
55
56    fn record(&self, name: impl Into<String>, outcome: &'static str) {
57        if let Ok(mut events) = self.trace.lock() {
58            events.push(TraceEvent {
59                name: name.into(),
60                outcome,
61            });
62        }
63    }
64}
65
66/// A description of a program. `A` is the success value, `E` is the expected
67/// error, and `S` is the service environment required to run it.
68pub struct Effect<A, E, S> {
69    run: Arc<dyn Fn(RunCtx<S>) -> BoxFuture<Result<A, Exit<E>>> + Send + Sync>,
70    _marker: PhantomData<fn() -> (A, E)>,
71}
72
73impl<A, E, S> Clone for Effect<A, E, S> {
74    fn clone(&self) -> Self {
75        Self {
76            run: Arc::clone(&self.run),
77            _marker: PhantomData,
78        }
79    }
80}
81
82impl<A, E, S> Effect<A, E, S>
83where
84    A: Send + Sync + 'static,
85    E: Send + Sync + 'static,
86    S: Send + Sync + 'static,
87{
88    fn new<F>(run: F) -> Self
89    where
90        F: Fn(RunCtx<S>) -> BoxFuture<Result<A, Exit<E>>> + Send + Sync + 'static,
91    {
92        Self {
93            run: Arc::new(run),
94            _marker: PhantomData,
95        }
96    }
97
98    pub fn succeed(value: A) -> Self
99    where
100        A: Clone,
101    {
102        let value = Arc::new(value);
103        Self::new(move |_ctx| {
104            let value = Arc::clone(&value);
105            Box::pin(async move { Ok((*value).clone()) })
106        })
107    }
108
109    pub fn fail(error: E) -> Self
110    where
111        E: Clone,
112    {
113        let error = Arc::new(error);
114        Self::new(move |_ctx| {
115            let error = Arc::clone(&error);
116            Box::pin(async move { Err(Exit::Fail((*error).clone())) })
117        })
118    }
119
120    pub fn die(message: impl Into<String>) -> Self {
121        let message = Arc::new(message.into());
122        Self::new(move |_ctx| {
123            let message = Arc::clone(&message);
124            Box::pin(async move { Err(Exit::Die((*message).clone())) })
125        })
126    }
127
128    /// Build a program from services and a cancellation token. The function
129    /// runs only when [`Effect::run`] is called.
130    pub fn from_async<F, Fut>(f: F) -> Self
131    where
132        F: Fn(Arc<S>, CancellationToken) -> Fut + Send + Sync + 'static,
133        Fut: Future<Output = Result<A, Exit<E>>> + Send + 'static,
134    {
135        let f = Arc::new(f);
136        Self::new(move |ctx| {
137            if ctx.cancel.is_cancelled() {
138                return Box::pin(async { Err(Exit::Interrupt) });
139            }
140            let fut = f(Arc::clone(&ctx.services), ctx.cancel.clone());
141            Box::pin(fut)
142        })
143    }
144
145    pub fn map<B, F>(&self, f: F) -> Effect<B, E, S>
146    where
147        B: Send + Sync + 'static,
148        F: Fn(A) -> B + Send + Sync + 'static,
149    {
150        let run = Arc::clone(&self.run);
151        let f = Arc::new(f);
152        Effect::new(move |ctx| {
153            let run = Arc::clone(&run);
154            let f = Arc::clone(&f);
155            Box::pin(async move { run(ctx).await.map(|value| f(value)) })
156        })
157    }
158
159    pub fn and_then<B, F>(&self, f: F) -> Effect<B, E, S>
160    where
161        B: Send + Sync + 'static,
162        F: Fn(A) -> Effect<B, E, S> + Send + Sync + 'static,
163    {
164        let run = Arc::clone(&self.run);
165        let f = Arc::new(f);
166        Effect::new(move |ctx| {
167            let run = Arc::clone(&run);
168            let f = Arc::clone(&f);
169            Box::pin(async move {
170                match run(ctx.clone()).await {
171                    Ok(value) => f(value).run_in(ctx).await,
172                    Err(error) => Err(error),
173                }
174            })
175        })
176    }
177
178    /// Handle an expected failure. Defects and interruption pass through.
179    pub fn catch_fail<F>(&self, f: F) -> Self
180    where
181        F: Fn(E) -> Effect<A, E, S> + Send + Sync + 'static,
182    {
183        let run = Arc::clone(&self.run);
184        let f = Arc::new(f);
185        Self::new(move |ctx| {
186            let run = Arc::clone(&run);
187            let f = Arc::clone(&f);
188            Box::pin(async move {
189                match run(ctx.clone()).await {
190                    Err(Exit::Fail(error)) => f(error).run_in(ctx).await,
191                    other => other,
192                }
193            })
194        })
195    }
196
197    /// Retry expected failures. Interruption and defects are not retried.
198    pub fn retry(&self, schedule: Schedule) -> Self
199    where
200        E: Clone,
201    {
202        let run = Arc::clone(&self.run);
203        Self::new(move |ctx| {
204            let run = Arc::clone(&run);
205            let mut schedule = schedule;
206            Box::pin(async move {
207                loop {
208                    if ctx.cancel.is_cancelled() {
209                        return Err(Exit::Interrupt);
210                    }
211                    match run(ctx.clone()).await {
212                        Ok(value) => return Ok(value),
213                        Err(Exit::Fail(_error)) if schedule.remaining > 0 => {
214                            schedule.remaining -= 1;
215                            ctx.record("retry", "fail");
216                            tokio::select! {
217                                _ = ctx.cancel.cancelled() => return Err(Exit::Interrupt),
218                                _ = tokio::time::sleep(schedule.delay) => {}
219                            }
220                        }
221                        Err(error) => return Err(error),
222                    }
223                }
224            })
225        })
226    }
227
228    pub fn timeout(&self, duration: Duration) -> Self {
229        let run = Arc::clone(&self.run);
230        Self::new(move |ctx| {
231            let run = Arc::clone(&run);
232            let child = ctx.child();
233            Box::pin(async move {
234                let child_cancel = child.cancel.clone();
235                let fut = run(child);
236                match tokio::time::timeout(duration, fut).await {
237                    Ok(result) => result,
238                    Err(_elapsed) => {
239                        child_cancel.cancel();
240                        Err(Exit::Interrupt)
241                    }
242                }
243            })
244        })
245    }
246
247    pub fn with_span(&self, name: impl Into<String>) -> Self {
248        let name = name.into();
249        let run = Arc::clone(&self.run);
250        Self::new(move |ctx| {
251            let run = Arc::clone(&run);
252            let name = name.clone();
253            let fut = run(ctx.clone());
254            Box::pin(async move {
255                let result = fut.await;
256                let outcome = match &result {
257                    Ok(_) => "ok",
258                    Err(Exit::Fail(_)) => "fail",
259                    Err(Exit::Die(_)) => "die",
260                    Err(Exit::Interrupt) => "interrupt",
261                };
262                ctx.record(name, outcome);
263                result
264            })
265        })
266    }
267
268    /// Run `left` and `right` together. The first expected failure or defect
269    /// cancels the sibling and waits for that sibling to finish.
270    pub fn zip_par<B>(&self, right: &Effect<B, E, S>) -> Effect<(A, B), E, S>
271    where
272        B: Send + Sync + 'static,
273    {
274        let left_run = Arc::clone(&self.run);
275        let right_run = Arc::clone(&right.run);
276        Effect::new(move |ctx| {
277            let left_run = Arc::clone(&left_run);
278            let right_run = Arc::clone(&right_run);
279            let left_ctx = ctx.child();
280            let right_ctx = ctx.child();
281            Box::pin(async move {
282                let left = left_run(left_ctx.clone());
283                let right = right_run(right_ctx.clone());
284                tokio::pin!(left, right);
285                tokio::select! {
286                    left_result = &mut left => match left_result {
287                        Ok(left_value) => match right.await {
288                            Ok(right_value) => Ok((left_value, right_value)),
289                            Err(error) => Err(error),
290                        },
291                        Err(error) => {
292                            right_ctx.cancel.cancel();
293                            let _ = right.await;
294                            Err(error)
295                        }
296                    },
297                    right_result = &mut right => match right_result {
298                        Ok(right_value) => match left.await {
299                            Ok(left_value) => Ok((left_value, right_value)),
300                            Err(error) => Err(error),
301                        },
302                        Err(error) => {
303                            left_ctx.cancel.cancel();
304                            let _ = left.await;
305                            Err(error)
306                        }
307                    },
308                }
309            })
310        })
311    }
312
313    /// First success or failure wins. The other side is cancelled and awaited.
314    pub fn race(&self, other: &Self) -> Self {
315        let left_run = Arc::clone(&self.run);
316        let right_run = Arc::clone(&other.run);
317        Self::new(move |ctx| {
318            let left_run = Arc::clone(&left_run);
319            let right_run = Arc::clone(&right_run);
320            let left_ctx = ctx.child();
321            let right_ctx = ctx.child();
322            Box::pin(async move {
323                let left = left_run(left_ctx.clone());
324                let right = right_run(right_ctx.clone());
325                tokio::pin!(left, right);
326                tokio::select! {
327                    left_result = &mut left => {
328                        right_ctx.cancel.cancel();
329                        let _ = right.await;
330                        left_result
331                    }
332                    right_result = &mut right => {
333                        left_ctx.cancel.cancel();
334                        let _ = left.await;
335                        right_result
336                    }
337                }
338            })
339        })
340    }
341
342    /// Acquire a resource, use it, and release it on success, expected
343    /// failure, and cancellation. Dropping the running future releases it too.
344    pub fn bracket<B, F, R>(&self, release: R, body: F) -> Effect<B, E, S>
345    where
346        A: Clone,
347        B: Send + Sync + 'static,
348        F: Fn(A) -> Effect<B, E, S> + Send + Sync + 'static,
349        R: Fn(A) + Send + Sync + 'static,
350    {
351        let acquire = Arc::clone(&self.run);
352        let release: Arc<dyn Fn(A) + Send + Sync> = Arc::new(release);
353        let body = Arc::new(body);
354        Effect::new(move |ctx| {
355            let acquire = Arc::clone(&acquire);
356            let release = Arc::clone(&release);
357            let body = Arc::clone(&body);
358            Box::pin(async move {
359                let resource = match acquire(ctx.clone()).await {
360                    Ok(resource) => resource,
361                    Err(error) => return Err(error),
362                };
363                let guard = ReleaseOnce::new(resource.clone(), Arc::clone(&release));
364                let result = body(resource).run_in(ctx).await;
365                guard.release_now();
366                result
367            })
368        })
369    }
370
371    /// Replace the service environment. The resulting program no longer
372    /// requires `S`; the caller supplies a different environment at the edge.
373    pub fn provide<S2>(self, services: S) -> Effect<A, E, S2>
374    where
375        S2: Send + Sync + 'static,
376    {
377        let services = Arc::new(services);
378        let run = self.run;
379        Effect::new(move |ctx| {
380            let run = Arc::clone(&run);
381            let services = Arc::clone(&services);
382            let ctx = RunCtx {
383                services,
384                cancel: ctx.cancel,
385                trace: ctx.trace,
386            };
387            run(ctx)
388        })
389    }
390
391    pub async fn run(self, services: Arc<S>) -> (Result<A, Exit<E>>, Vec<TraceEvent>) {
392        let trace = Arc::new(Mutex::new(Vec::new()));
393        let ctx = RunCtx {
394            services,
395            cancel: CancellationToken::new(),
396            trace: Arc::clone(&trace),
397        };
398        let result = self.run_in(ctx).await;
399        let events = trace
400            .lock()
401            .map(|events| events.clone())
402            .unwrap_or_default();
403        (result, events)
404    }
405
406    pub(crate) async fn run_in(&self, ctx: RunCtx<S>) -> Result<A, Exit<E>> {
407        (self.run)(ctx).await
408    }
409}
410
411#[derive(Debug, Clone, Copy)]
412pub struct Schedule {
413    pub remaining: u32,
414    pub delay: Duration,
415}
416
417struct ReleaseOnce<T> {
418    value: Mutex<Option<T>>,
419    release: Arc<dyn Fn(T) + Send + Sync>,
420}
421
422impl<T> ReleaseOnce<T> {
423    fn new(value: T, release: Arc<dyn Fn(T) + Send + Sync>) -> Self {
424        Self {
425            value: Mutex::new(Some(value)),
426            release,
427        }
428    }
429
430    fn release_now(&self) {
431        if let Ok(mut slot) = self.value.lock() {
432            if let Some(value) = slot.take() {
433                (self.release)(value);
434            }
435        }
436    }
437}
438
439impl<T> Drop for ReleaseOnce<T> {
440    fn drop(&mut self) {
441        self.release_now();
442    }
443}