Skip to main content

rx_rust/operators/utility/
delay.rs

1use crate::disposable::{Disposable, bound_drop_disposal::BoundDropDisposal};
2use crate::utils::pending_events::EventBatch;
3use crate::utils::serialized_delivery::{DeliveryStopped, UpdateOutcome};
4use crate::utils::subscribe_with_context::{self, SubscriptionContext, subscribe_with_context};
5use crate::utils::subscription_slot::SubscriptionSlot;
6use crate::utils::types::{MarkerType, MaybeSend};
7use crate::{
8    observable::Observable,
9    observable::Subscription,
10    observer::{Flow, Observer, Termination},
11    scheduler::{RecursionAction, Scheduler},
12};
13use educe::Educe;
14use std::{
15    collections::VecDeque,
16    time::{Duration, Instant},
17};
18
19/// Shifts the emissions from an Observable forward in time by a specified duration.
20/// See <https://reactivex.io/documentation/operators/delay.html>
21///
22/// # Examples
23/// ```rust
24/// # #[cfg(not(feature = "tokio-scheduler"))]
25/// # fn main() {}
26/// # #[cfg(feature = "tokio-scheduler")]
27/// #[tokio::main]
28/// async fn main() {
29///     use rx_rust::{
30///         observable::ObservableExt,
31///         observer::Termination,
32///         operators::{
33///             creating::from_iter::FromIter,
34///             utility::delay::Delay,
35///         },
36///     };
37///     use std::{
38///         sync::{Arc, Mutex},
39///         time::Duration,
40///     };
41///     use tokio::time::sleep;
42///
43///     let handle = tokio::runtime::Handle::current();
44///     let values = Arc::new(Mutex::new(Vec::new()));
45///     let terminations = Arc::new(Mutex::new(Vec::new()));
46///     let values_observer = Arc::clone(&values);
47///     let terminations_observer = Arc::clone(&terminations);
48///
49///     let subscription = Delay::new(
50///         FromIter::new(vec![1, 2, 3]),
51///         Duration::from_millis(5),
52///         handle.clone(),
53///     )
54///     .subscribe_with_callback(
55///         move |value| values_observer.lock().unwrap().push(value),
56///         move |termination| terminations_observer
57///             .lock()
58///             .unwrap()
59///             .push(termination),
60///     );
61///
62///     sleep(Duration::from_millis(10)).await;
63///     drop(subscription);
64///
65///     assert_eq!(&*values.lock().unwrap(), &[1, 2, 3]);
66///     assert_eq!(
67///         &*terminations.lock().unwrap(),
68///         &[Termination::Completed]
69///     );
70/// }
71/// ```
72#[derive(Educe)]
73#[educe(Debug, Clone)]
74pub struct Delay<'or, OE, S> {
75    source: OE,
76    delay: Duration,
77    scheduler: S,
78    _marker: MarkerType<&'or ()>,
79}
80
81impl<'or, OE, S> Delay<'or, OE, S> {
82    pub fn new(source: OE, delay: Duration, scheduler: S) -> Self {
83        Self {
84            source,
85            delay,
86            scheduler,
87            _marker: Default::default(),
88        }
89    }
90}
91
92impl<'or, T, E, OE, S> Observable<'static, T, E> for Delay<'or, OE, S>
93where
94    T: MaybeSend + 'static,
95    E: MaybeSend + 'static,
96    OE: Observable<'or, T, E>,
97    S: Scheduler + Clone + MaybeSend + 'static,
98{
99    type D = subscribe_with_context::Disposal<'or, OE::D>;
100
101    fn subscribe(
102        self,
103        observer: impl Observer<T, E> + MaybeSend + 'static,
104    ) -> Subscription<Self::D> {
105        let model = Model::<T, S::D> {
106            values: VecDeque::new(),
107            completion: None,
108            timer: SubscriptionSlot::Idle,
109        };
110        subscribe_with_context(observer, model, |context| {
111            self.source.subscribe(DelayObserver {
112                context,
113                delay: self.delay,
114                scheduler: self.scheduler,
115            })
116        })
117    }
118}
119
120/// The events waiting for their deadline, and the recursive scheduler task that delivers them.
121struct Model<T, D: Disposable> {
122    /// The values with the instant at which each of them is due, in ascending order.
123    values: VecDeque<(Instant, T)>,
124    /// The instant at which the completion is due, once the source has completed.
125    completion: Option<Instant>,
126    /// Keeps at most one recursive scheduler task alive while events are waiting. The slot is
127    /// reserved while the task is being scheduled, which covers schedulers that can execute a
128    /// zero-delay task before returning its disposal.
129    timer: SubscriptionSlot<BoundDropDisposal<D>>,
130}
131
132impl<T, D: Disposable> Model<T, D> {
133    /// Returns the instant at which the next event is due.
134    fn next_deadline(&self) -> Option<Instant> {
135        self.values
136            .front()
137            .map(|(deadline, _)| *deadline)
138            .or(self.completion)
139    }
140}
141
142struct DelayObserver<T, E, OR, S: Scheduler> {
143    context: SubscriptionContext<T, E, OR, Model<T, S::D>>,
144    delay: Duration,
145    scheduler: S,
146}
147
148impl<T, E, OR, S> DelayObserver<T, E, OR, S>
149where
150    T: MaybeSend + 'static,
151    E: MaybeSend + 'static,
152    OR: Observer<T, E> + MaybeSend + 'static,
153    S: Scheduler + Clone + MaybeSend + 'static,
154{
155    /// Queues `value`, or the completion when it is `None`, and starts the timer if needed.
156    ///
157    /// Returns [`Flow::Stop`] once the context has stopped: the event was then dropped, and so
158    /// would every later one be. What downstream itself answers is only known once the timer
159    /// fires, so this is otherwise [`Flow::Continue`].
160    fn queue_event(&self, value: Option<T>) -> Flow {
161        let timer_setup = self.context.update(|model| {
162            let deadline = Instant::now() + self.delay;
163            match value {
164                Some(value) => model.values.push_back((deadline, value)),
165                None => model.completion = Some(deadline),
166            }
167            let start_timer = model.timer.reserve_if_idle();
168            UpdateOutcome::new(start_timer.then_some(deadline))
169        });
170        let deadline = match timer_setup {
171            Ok(Some(deadline)) => deadline,
172            // The timer is already running, and will find the event when it fires.
173            Ok(None) => return Flow::Continue,
174            Err(DeliveryStopped) => return Flow::Stop,
175        };
176
177        let weak_context = self.context.downgrade();
178        let disposal = self.scheduler.schedule_recursively(
179            move |_| {
180                let Some(context) = weak_context.upgrade() else {
181                    return RecursionAction::Stop;
182                };
183                context
184                    .update(|model| {
185                        let now = Instant::now();
186                        // The deadlines are ascending, so one binary search splits the queue into
187                        // the due values and the ones that keep waiting.
188                        let due = model
189                            .values
190                            .partition_point(|(deadline, _)| *deadline <= now);
191                        let values: Vec<T> =
192                            model.values.drain(..due).map(|(_, value)| value).collect();
193
194                        // The completion is queued last, so it is due only once no value is left
195                        // to deliver before it and its own deadline has passed. A value that
196                        // still waits holds the completion back, which keeps the order right.
197                        if model.values.is_empty()
198                            && model.completion.is_some_and(|deadline| deadline <= now)
199                        {
200                            return UpdateOutcome::new(RecursionAction::Stop)
201                                .with_events(EventBatch::NextBatchAndTermination(
202                                    values,
203                                    Termination::Completed,
204                                ))
205                                .with_drop_outside(model.timer.release());
206                        }
207
208                        match model.next_deadline() {
209                            Some(deadline) => {
210                                UpdateOutcome::new(RecursionAction::ContinueAt(deadline))
211                                    .with_events(EventBatch::NextBatch(values))
212                                    .without_drop_outside()
213                            }
214                            // Nothing is waiting anymore: stop the timer until the next event.
215                            None => UpdateOutcome::new(RecursionAction::Stop)
216                                .with_events(EventBatch::NextBatch(values))
217                                .with_drop_outside(model.timer.release()),
218                        }
219                    })
220                    .unwrap_or(RecursionAction::Stop)
221            },
222            Some(deadline.saturating_duration_since(Instant::now())),
223        );
224
225        // A zero delay can fire the timer before this runs, and that firing can end the stream:
226        // the flow of this update is what reports it.
227        self.context.update_flow(move |model| {
228            // If the timer already stopped (possible for a zero delay), `fill` gives the handle
229            // back to dispose outside the lock.
230            UpdateOutcome::empty().with_drop_outside(model.timer.fill(disposal))
231        })
232    }
233}
234
235impl<T, E, OR, S> Observer<T, E> for DelayObserver<T, E, OR, S>
236where
237    T: MaybeSend + 'static,
238    E: MaybeSend + 'static,
239    OR: Observer<T, E> + MaybeSend + 'static,
240    S: Scheduler + Clone + MaybeSend + 'static,
241{
242    fn on_next(&mut self, value: T) -> Flow {
243        self.queue_event(Some(value))
244    }
245
246    fn on_termination(self, termination: Termination<E>) {
247        match termination {
248            // The completion is the last event, so what the context answers is of no use here.
249            Termination::Completed => {
250                let _ = self.queue_event(None);
251            }
252            // An error is not delayed: it terminates the subscription right away, which drops the
253            // values that are still waiting along with the timer.
254            error @ Termination::Error(_) => {
255                self.context.send_termination(error);
256            }
257        }
258    }
259}