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}