Skip to main content

rx_rust/operators/utility/
observe_on.rs

1use crate::utils::serialized_delivery::{DeliveryStopped, UpdateOutcome};
2use crate::{
3    disposable::{Disposable, bound_drop_disposal::BoundDropDisposal},
4    observable::{Observable, Subscription},
5    observer::{Event, Flow, Observer, Termination},
6    scheduler::{RecursionAction, Scheduler},
7    utils::{
8        pending_events::EventBatch,
9        subscribe_with_context::{self, SubscriptionContext, subscribe_with_context},
10        subscription_slot::SubscriptionSlot,
11        types::{MarkerType, MaybeSend},
12    },
13};
14use educe::Educe;
15
16/// Specifies the `Scheduler` on which an observer will observe this Observable.
17/// See <https://reactivex.io/documentation/operators/observeon.html>
18///
19/// # Examples
20/// ```rust
21/// # #[cfg(not(feature = "tokio-scheduler"))]
22/// # fn main() {}
23/// # #[cfg(feature = "tokio-scheduler")]
24/// #[tokio::main]
25/// async fn main() {
26///     use rx_rust::{
27///         observable::ObservableExt,
28///         observer::Termination,
29///         operators::{
30///             creating::from_iter::FromIter,
31///             utility::observe_on::ObserveOn,
32///         },
33///     };
34///     use std::sync::{Arc, Mutex};
35///     use tokio::time::{sleep, Duration};
36///
37///     let handle = tokio::runtime::Handle::current();
38///     let values = Arc::new(Mutex::new(Vec::new()));
39///     let terminations = Arc::new(Mutex::new(Vec::new()));
40///     let values_observer = Arc::clone(&values);
41///     let terminations_observer = Arc::clone(&terminations);
42///
43///     let subscription = ObserveOn::new(FromIter::new(vec![1, 2, 3]), handle.clone())
44///         .subscribe_with_callback(
45///             move |value| values_observer.lock().unwrap().push(value),
46///             move |termination| terminations_observer
47///                 .lock()
48///                 .unwrap()
49///                 .push(termination),
50///         );
51///
52///     sleep(Duration::from_millis(10)).await;
53///     drop(subscription);
54///
55///     assert_eq!(&*values.lock().unwrap(), &[1, 2, 3]);
56///     assert_eq!(
57///         &*terminations.lock().unwrap(),
58///         &[Termination::Completed]
59///     );
60/// }
61/// ```
62#[derive(Educe)]
63#[educe(Debug, Clone)]
64pub struct ObserveOn<'or, OE, S> {
65    source: OE,
66    scheduler: S,
67    _marker: MarkerType<&'or ()>,
68}
69
70impl<'or, OE, S> ObserveOn<'or, OE, S> {
71    pub fn new(source: OE, scheduler: S) -> Self {
72        Self {
73            source,
74            scheduler,
75            _marker: Default::default(),
76        }
77    }
78}
79
80impl<'or, T, E, OE, S> Observable<'static, T, E> for ObserveOn<'or, OE, S>
81where
82    T: MaybeSend + 'static,
83    E: MaybeSend + 'static,
84    OE: Observable<'or, T, E>,
85    S: Scheduler + Clone + MaybeSend + 'static,
86{
87    type D = subscribe_with_context::Disposal<'or, OE::D>;
88
89    fn subscribe(
90        self,
91        observer: impl Observer<T, E> + MaybeSend + 'static,
92    ) -> Subscription<Self::D> {
93        let model = Model::<T, E, S::D> {
94            values: Vec::new(),
95            termination: None,
96            task: SubscriptionSlot::Idle,
97        };
98        subscribe_with_context(observer, model, |context| {
99            self.source.subscribe(ObserveOnObserver {
100                context,
101                scheduler: self.scheduler,
102            })
103        })
104    }
105}
106
107/// Events waiting to be observed and the scheduler task that delivers them.
108struct Model<T, E, D: Disposable> {
109    values: Vec<T>,
110    termination: Option<Termination<E>>,
111    /// Keeps at most one recursive scheduler task alive while events are waiting. The slot is
112    /// reserved while the task is being scheduled, which covers schedulers that can execute it
113    /// before returning its disposal.
114    task: SubscriptionSlot<BoundDropDisposal<D>>,
115}
116
117struct ObserveOnObserver<T, E, OR, S: Scheduler> {
118    context: SubscriptionContext<T, E, OR, Model<T, E, S::D>>,
119    scheduler: S,
120}
121
122impl<T, E, OR, S: Scheduler> ObserveOnObserver<T, E, OR, S> {
123    /// Queues `event` for the observing scheduler, starting the delivering task if it is stopped.
124    ///
125    /// Only one task exists at a time. `Observer` serializes its callers — `on_next` takes
126    /// `&mut self` and `on_termination` takes `self` — so a task cannot be started here while
127    /// another call is between starting a task and storing its disposal below.
128    ///
129    /// Returns [`Flow::Stop`] once the context has stopped: the event was then dropped, and so
130    /// would every later one be. What downstream itself answers is only known once the task
131    /// delivers, so this is otherwise [`Flow::Continue`].
132    fn queue_event(&self, event: Event<T, E>) -> Flow
133    where
134        T: MaybeSend + 'static,
135        E: MaybeSend + 'static,
136        OR: Observer<T, E> + MaybeSend + 'static,
137        S: Scheduler + Clone + MaybeSend + 'static,
138    {
139        let task_setup = self.context.update(|model| {
140            match event {
141                Event::Next(value) => model.values.push(value),
142                Event::Termination(termination) => model.termination = Some(termination),
143            }
144            UpdateOutcome::new(model.task.reserve_if_idle())
145        });
146        match task_setup {
147            Ok(true) => {}
148            // The task is already running, and will find the event on its next pass.
149            Ok(false) => return Flow::Continue,
150            Err(DeliveryStopped) => return Flow::Stop,
151        }
152
153        // The context owns this task through the model, so the task only holds a weak reference
154        // back: a strong one would form a cycle and leak the subscription.
155        let weak_context = self.context.downgrade();
156        let task = self.scheduler.schedule_recursively(
157            move |_| {
158                let Some(context) = weak_context.upgrade() else {
159                    return RecursionAction::Stop;
160                };
161                context
162                    .update(|model| {
163                        let termination = model.termination.take();
164                        let values = std::mem::take(&mut model.values);
165                        let (action, events, discarded_values) = match termination {
166                            // Nothing left to deliver. An empty batch is a no-op for the context,
167                            // and every branch must produce one so their types agree.
168                            None if values.is_empty() => (
169                                RecursionAction::Stop,
170                                EventBatch::NextBatch(Vec::new()),
171                                None,
172                            ),
173                            // Recur instead of stopping: values arriving while this batch is
174                            // delivered are pushed onto the model, and only another pass takes
175                            // them. They cannot start a task of their own, because this one is
176                            // still `Running` until a pass finds the model empty.
177                            None => (
178                                RecursionAction::ContinueImmediately,
179                                EventBatch::NextBatch(values),
180                                None,
181                            ),
182                            Some(completion @ Termination::Completed) => (
183                                RecursionAction::Stop,
184                                EventBatch::NextBatchAndTermination(values, completion),
185                                None,
186                            ),
187                            // An error preempts the values buffered before it, unlike a completion.
188                            Some(error @ Termination::Error(_)) => (
189                                RecursionAction::Stop,
190                                EventBatch::Termination(error),
191                                Some(values),
192                            ),
193                        };
194                        let finished_task = match action {
195                            RecursionAction::Stop => model.task.release(),
196                            _ => None,
197                        };
198                        UpdateOutcome::new(action)
199                            .with_events(events)
200                            .with_drop_outside((finished_task, discarded_values))
201                    })
202                    .unwrap_or(RecursionAction::Stop)
203            },
204            None,
205        );
206
207        // A scheduler that runs the task at once can have delivered, and ended, the stream before
208        // this runs: the flow of this update is what reports it.
209        self.context.update_flow(move |model| {
210            // If the task already stopped, `fill` gives the handle back to dispose outside the
211            // lock.
212            UpdateOutcome::empty().with_drop_outside(model.task.fill(task))
213        })
214    }
215}
216
217impl<T, E, OR, S> Observer<T, E> for ObserveOnObserver<T, E, OR, S>
218where
219    T: MaybeSend + 'static,
220    E: MaybeSend + 'static,
221    OR: Observer<T, E> + MaybeSend + 'static,
222    S: Scheduler + Clone + MaybeSend + 'static,
223{
224    fn on_next(&mut self, value: T) -> Flow {
225        self.queue_event(Event::Next(value))
226    }
227
228    fn on_termination(self, termination: Termination<E>) {
229        // The termination is the last event, so what the context answers is of no use here.
230        let _ = self.queue_event(Event::Termination(termination));
231    }
232}