Skip to main content

rx_rust/subject/
unicast_subject.rs

1//! A single-consumer pipe between an [`Observer`] and an [`Observable`].
2//!
3//! Unlike the multicast subjects, a unicast subject serves exactly one observer, which is what
4//! lets it buffer the events that arrive before the subscription instead of dropping them, and
5//! lets it move each value to that observer instead of cloning it.
6//!
7//! The two ends are separate values: [`UnicastSender`] is the [`Observer`] and
8//! [`UnicastObservable`] is the [`Observable`]. Neither is [`Clone`], so the type system, rather
9//! than a runtime check, is what guarantees that the pipe is fed by one sender and consumed by one
10//! observer. That is also why a unicast subject does not implement the [`Subject`] trait, whose
11//! implementors are both an [`Observable`] and an [`Observer`] at the same time, and why it cannot
12//! be used to multicast a source through [`ObservableExt::multicast`].
13//!
14//! [`Subject`]: crate::subject::Subject
15//! [`ObservableExt::multicast`]: crate::observable::ObservableExt::multicast
16
17use crate::{
18    disposable::Disposable,
19    observable::{Observable, Subscription},
20    observer::{Event, Flow, Observer, Termination, boxed_observer::BoxedObserver},
21    utils::{
22        mutable::{Mutable, MutableBool, MutableBoolHelper, MutableExt, MutableHelper},
23        on_panic::on_panic,
24        pending_events::PendingEvents,
25        types::{MaybeSend, Shared},
26    },
27};
28use educe::Educe;
29
30/// Creates a unicast subject, giving back its sending and its observable end.
31///
32/// Values sent before the subscription are buffered and replayed to the observer when it
33/// subscribes, followed by the termination if the sender already terminated. Once the observer is
34/// gone, by disposing its subscription or by dropping the [`UnicastObservable`] without
35/// subscribing, later events are dropped.
36///
37/// # Releasing the observer
38///
39/// Disposing the subscription does not necessarily drop the observer where it happens: between two
40/// events the sender holds it, which is what lets it deliver a value without taking a lock, and
41/// only the sender can let go of it. It does so at the first of these:
42///
43/// - the end of the notification the disposal happened in, which is the usual case, because a
44///   consumer that stops a stream normally does it from inside the notification of a value;
45/// - the next event the sender sends, which is dropped along with the observer;
46/// - the drop of the sender.
47///
48/// So an observer whose subscription is disposed between two events, by a consumer that is not the
49/// one being notified, stays alive until the producer sends again or goes away. A producer that
50/// might go quiet for a long time can use [`UnicastSender::is_disposed`], which is true as soon as
51/// the disposal happens, to drop its sender and release the observer with it.
52///
53/// # Examples
54/// ```rust
55/// use rx_rust::{
56///     observable::ObservableExt,
57///     observer::{Observer, Termination},
58///     subject::unicast_subject::unicast_subject,
59/// };
60/// use std::{
61///     convert::Infallible,
62///     sync::{Arc, Mutex},
63/// };
64///
65/// let (mut sender, observable) = unicast_subject::<i32, Infallible>();
66///
67/// // The values sent before the subscription are buffered instead of being dropped.
68/// sender.on_next(111);
69/// sender.on_next(222);
70///
71/// let values = Arc::new(Mutex::new(Vec::new()));
72/// let values_observer = Arc::clone(&values);
73/// let subscription = observable.subscribe_with_callback(
74///     move |value| values_observer.lock().unwrap().push(value),
75///     |_| {},
76/// );
77/// assert_eq!(&*values.lock().unwrap(), &[111, 222]);
78///
79/// sender.on_next(333);
80/// assert_eq!(&*values.lock().unwrap(), &[111, 222, 333]);
81///
82/// sender.on_termination(Termination::Completed);
83/// drop(subscription);
84/// ```
85pub fn unicast_subject<'or, T, E>() -> (UnicastSender<'or, T, E>, UnicastObservable<'or, T, E>) {
86    new_pair(PendingEvents::new())
87}
88
89/// Creates a unicast subject whose buffer is pre-allocated for `capacity` values.
90///
91/// The capacity is only a hint: the buffer still grows as needed. See [`unicast_subject`] for the
92/// behavior of the returned pair.
93pub fn unicast_subject_with_capacity<'or, T, E>(
94    capacity: usize,
95) -> (UnicastSender<'or, T, E>, UnicastObservable<'or, T, E>) {
96    new_pair(PendingEvents::with_capacity(capacity))
97}
98
99fn new_pair<'or, T, E>(
100    pending: PendingEvents<T, E>,
101) -> (UnicastSender<'or, T, E>, UnicastObservable<'or, T, E>) {
102    let pipe = Shared::new(Pipe {
103        is_disposed: MutableBool::new(false),
104        state: Mutable::new(State::Pending(pending)),
105    });
106    (
107        UnicastSender {
108            pipe: pipe.clone(),
109            observer: None,
110        },
111        UnicastObservable(Some(pipe)),
112    )
113}
114
115#[derive(Educe)]
116#[educe(Debug)]
117enum State<'or, T, E> {
118    /// The observer is not held here, so the events that arrive wait in the queue: before the
119    /// subscription, and while the buffered events are being replayed outside the lock.
120    Pending(PendingEvents<T, E>),
121    /// The observer has subscribed and is idle, waiting for the sender to pick it up.
122    Attached(BoxedObserver<'or, T, E>),
123    /// The sender holds the observer and delivers to it on its own, so nothing waits here: the
124    /// sender is the only one that queues events, and it has nothing left to queue them for.
125    Held,
126    /// The observer is gone, either because it was terminated or because the subscription was
127    /// disposed. Every later event is dropped.
128    Closed,
129}
130
131/// The pipe itself, which its sending and its observable end share.
132#[derive(Educe)]
133#[educe(Debug)]
134struct Pipe<'or, T, E> {
135    /// Whether the observer went away, which is the one thing the sender still has to learn from
136    /// here once it holds the observer itself. Reading it takes no lock, which is what lets the
137    /// sender check it before and after every event it delivers on its own.
138    ///
139    /// This is not a copy of [`State::Closed`], and only [`close`] raises it: it says that the
140    /// observer was taken away from the pipe, not that the pipe is over. Terminating the pipe and
141    /// dropping the sender close the state without touching it, because the sender is gone by then
142    /// and the sender is its only reader. An observer that answers [`Flow::Stop`] is taken away
143    /// too, so the sender closes the pipe there, which raises this as any other disposal does.
144    is_disposed: MutableBool,
145    state: Mutable<State<'or, T, E>>,
146}
147
148type SharedPipe<'or, T, E> = Shared<Pipe<'or, T, E>>;
149
150/// The sending end of a unicast subject. See [`unicast_subject`].
151///
152/// Dropping the sender without terminating it closes the pipe, which drops the observer without
153/// notifying it: no event can reach it anymore, because the sender was the only way in, but a
154/// producer that gave up halfway has not completed anything either. That is also what releases an
155/// observer whose subscription was disposed while the sender was idle, as [`unicast_subject`]
156/// describes.
157#[derive(Educe)]
158#[educe(Debug)]
159pub struct UnicastSender<'or, T, E> {
160    pipe: SharedPipe<'or, T, E>,
161    /// The observer, held here instead of in the shared state so that sending an event takes no
162    /// lock at all: the sender is the only producer of the pipe, so nothing else has to reach the
163    /// observer while it is idle.
164    ///
165    /// It is taken out of [`State::Attached`] by the first event that finds it parked there, and
166    /// stays here until the pipe ends. The state of the pipe is [`State::Held`] meanwhile, which
167    /// carries nothing: nothing can be queued behind an observer that only the sender feeds.
168    ///
169    /// The price is that [`close`] cannot drop the observer anymore, because the observer is not
170    /// in the state it closes. The sender drops it instead, as soon as it sees
171    /// [`Pipe::is_disposed`], and at the latest when the sender itself is dropped.
172    observer: Option<BoxedObserver<'or, T, E>>,
173}
174
175impl<T, E> Drop for UnicastSender<'_, T, E> {
176    fn drop(&mut self) {
177        // The pipe is over, so the observer held here is dropped without being notified, like the
178        // one the state below holds.
179        let observer = self.observer.take();
180        // Terminating the pipe consumes the sender, so this also runs right after the last event
181        // was queued. That event still has to reach the observer, whether it is waiting in the
182        // queue for a late subscriber or for the delivery that is running.
183        let previous_state = self.pipe.state.with_mut(|current| match current {
184            State::Pending(pending) if pending.is_terminated() => None,
185            state => Some(std::mem::replace(state, State::Closed)),
186        });
187        drop(observer); // Drop outside the lock to avoid potential deadlock
188        drop(previous_state); // Drop outside the lock to avoid potential deadlock
189    }
190}
191
192impl<T, E> UnicastSender<'_, T, E> {
193    /// Returns whether the observer is gone, which happens when its subscription is disposed or
194    /// when the [`UnicastObservable`] is dropped without being subscribed to.
195    ///
196    /// Every later event is dropped, so a producer can use this to stop producing.
197    pub fn is_disposed(&self) -> bool {
198        // This is exactly what the flag says, and reading it takes no lock. The state would say
199        // the same, because the only other way to close it consumes the sender.
200        self.pipe.is_disposed.read()
201    }
202}
203
204impl<T, E> Observer<T, E> for UnicastSender<'_, T, E> {
205    fn on_next(&mut self, value: T) -> Flow {
206        if self.observer.is_some() {
207            return self.send_held(value);
208        }
209        // Whatever the value ends up as when it is not delivered - rejected by the queue or
210        // dropped by a closed pipe - is handed back, to be dropped once the lock is released.
211        let (delivery, rejected, discarded) = self.pipe.state.with_mut(|current| match current {
212            State::Pending(pending) => {
213                // Terminating consumes the sender, so no value can arrive after the termination.
214                (None, pending.push(Event::Next(value)), None)
215            }
216            state @ State::Attached(_) => {
217                let State::Attached(observer) = std::mem::replace(state, State::Held) else {
218                    unreachable!()
219                };
220                (Some((observer, value)), None, None)
221            }
222            // The sender takes the fast path above while it holds the observer, so it never looks
223            // at a state it is itself the subject of.
224            State::Held => unreachable!(),
225            State::Closed => (None, None, Some(value)),
226        });
227        // Asserted with the lock released: a failing assertion would otherwise unwind while
228        // holding it, which poisons it for every later event.
229        debug_assert!(rejected.is_none());
230        // A discarded value is one the closed pipe had nowhere to deliver to, and so is every
231        // later one: that is the answer, and it needs no second look at the flag.
232        let flow = if discarded.is_some() {
233            Flow::Stop
234        } else {
235            Flow::Continue
236        };
237        drop((rejected, discarded)); // Drop outside the lock to avoid potential deadlock
238        if let Some((observer, value)) = delivery {
239            // The observer stays here from now on, so this is the last event that has to look for
240            // it in the state of the pipe.
241            self.observer = Some(observer);
242            return self.send_held(value);
243        }
244        flow
245    }
246
247    fn on_termination(mut self, termination: Termination<E>) {
248        if let Some(observer) = self.observer.take() {
249            // The observer is held here, so the state only has to be closed, and the drop of the
250            // sender that runs right after this has nothing left to close or to hand over. It is
251            // [`State::Held`], unless a disposal closed it while the observer was held here.
252            let previous_state = self.pipe.state.replace_value(State::Closed);
253            let is_disposed = matches!(previous_state, State::Closed);
254            drop(previous_state); // Drop outside the lock to avoid potential deadlock
255            if is_disposed {
256                // The subscription was disposed while the observer was held here, so nothing is
257                // notified anymore: the observer is only dropped, as `close` could not.
258                drop(observer);
259                drop(termination);
260            } else {
261                observer.on_termination(termination); // Notify outside the lock
262            }
263            return;
264        }
265        // Like in `on_next`, a termination that is not delivered is handed back to be dropped
266        // once the lock is released.
267        let (delivery, rejected, discarded) = self.pipe.state.with_mut(|current| match current {
268            State::Pending(pending) => {
269                // Terminating consumes the sender, so it cannot be terminated twice.
270                (None, pending.push(Event::Termination(termination)), None)
271            }
272            state @ State::Attached(_) => {
273                let State::Attached(observer) = std::mem::replace(state, State::Closed) else {
274                    unreachable!()
275                };
276                (Some((observer, termination)), None, None)
277            }
278            // Terminating while the sender holds the observer is the fast path above.
279            State::Held => unreachable!(),
280            State::Closed => (None, None, Some(termination)),
281        });
282        // Asserted with the lock released: a failing assertion would otherwise unwind while
283        // holding it, which poisons it for every later event.
284        debug_assert!(rejected.is_none());
285        drop((rejected, discarded)); // Drop outside the lock to avoid potential deadlock
286        if let Some((observer, termination)) = delivery {
287            observer.on_termination(termination); // Notify outside the lock
288        }
289    }
290}
291
292impl<T, E> UnicastSender<'_, T, E> {
293    /// Sends `value` to the observer held by the sender, without taking the lock.
294    ///
295    /// The flag is what tells the sender that the observer went away while it was held here, so it
296    /// is read before the notification, to drop the value instead of delivering it. It is read
297    /// after the notification too: an observer that answered [`Flow::Continue`] can still have
298    /// been disposed by that very notification, which the flag reports and the answer cannot.
299    /// Nothing else is needed: the state cannot change under a pipe whose only producer is the
300    /// caller.
301    fn send_held(&mut self, value: T) -> Flow {
302        debug_assert!(self.observer.is_some());
303        if self.pipe.is_disposed.read() {
304            let observer = self.observer.take();
305            // The state is closed already, and no lock is held here anyway, so both are simply
306            // dropped where they are.
307            drop(observer);
308            drop(value);
309            return Flow::Stop;
310        }
311        let mut flow = Flow::Continue;
312        if let Some(observer) = &mut self.observer {
313            flow = observer.on_next(value); // Notify without taking the lock
314        }
315        // Disposing from inside that notification is how a consumer usually stops a stream, so the
316        // flag is read once more to release the observer right away instead of at the next event.
317        if flow.is_stop() || self.pipe.is_disposed.read() {
318            let observer = self.observer.take();
319            // An observer that ended its own stream leaves the pipe with nothing to deliver to,
320            // which is what a disposal leaves it with too: closing it keeps the state and the flag
321            // in step with the observer the sender has just let go of, so that the next event
322            // takes the closed path instead of looking for an observer that is gone.
323            close(&self.pipe);
324            drop(observer); // Drop outside the lock to avoid potential deadlock
325            return Flow::Stop;
326        }
327        Flow::Continue
328    }
329}
330
331/// The observable end of a unicast subject. See [`unicast_subject`].
332///
333/// [`Observable::subscribe`] consumes it, so the pipe cannot be subscribed to twice.
334#[derive(Educe)]
335#[educe(Debug)]
336pub struct UnicastObservable<'or, T, E>(Option<SharedPipe<'or, T, E>>);
337
338impl<T, E> Drop for UnicastObservable<'_, T, E> {
339    fn drop(&mut self) {
340        // `None` when it has been subscribed to, which moves the shared state into the disposal.
341        if let Some(pipe) = self.0.take() {
342            close(&pipe);
343        }
344    }
345}
346
347impl<'or, T, E> Observable<'or, T, E> for UnicastObservable<'or, T, E> {
348    type D = Disposal<'or, T, E>;
349
350    fn subscribe(
351        mut self,
352        observer: impl Observer<T, E> + MaybeSend + 'or,
353    ) -> Subscription<Self::D> {
354        let pipe = self
355            .0
356            .take()
357            .expect("the shared state is taken by either subscribing or dropping");
358        // The replay takes the pipe as it finds it: it parks the observer when nothing waits, it
359        // replays what does, and it drops the observer when the sender is already gone. Asking the
360        // state about that beforehand would only be one more lock for the answer it takes anyway.
361        // The observer is parked into `State::Attached`, from where the next event the sender
362        // sends picks it up for good, unless the replay ends the pipe with a buffered termination.
363        let is_live = deliver(&pipe, BoxedObserver::new(observer));
364        // A pipe that is over stays over, so the subscription has nothing left to dispose of and
365        // does not have to keep the pipe alive until the consumer drops it.
366        Subscription::new(Disposal(is_live.then_some(pipe)))
367    }
368}
369
370/// The disposal of a [`UnicastObservable`] subscription.
371///
372/// It holds no pipe when the pipe was already over by the end of the subscription, which is the
373/// only thing there is to know about it: a pipe that is over cannot be disposed of anymore, and a
374/// pipe that is not cannot become so on its own. Holding nothing releases the pipe right away
375/// instead of when the subscription is dropped, and costs nothing to carry: a [`Shared`] is a
376/// pointer, so wrapping it in an [`Option`] does not make it any bigger.
377#[derive(Educe)]
378#[educe(Debug)]
379pub struct Disposal<'or, T, E>(Option<SharedPipe<'or, T, E>>);
380
381impl<T, E> Disposable for Disposal<'_, T, E> {
382    fn dispose(self) {
383        if let Some(pipe) = self.0 {
384            close(&pipe);
385        }
386    }
387}
388
389/// Closes the pipe, so that every later event is dropped.
390///
391/// The observer is dropped here when the state holds it. When the sender holds it instead, only
392/// the flag below can reach the sender: the observer is then dropped by the sender, on its next
393/// event or when it is dropped itself.
394fn close<T, E>(pipe: &SharedPipe<'_, T, E>) {
395    // Raised before the state is replaced, so that the sender never delivers an event to an
396    // observer that the state has already given up on, and raised under the lock, so that a sender
397    // that read it cannot find the state still [`State::Held`]: it reads the flag without the lock,
398    // then drops the observer it holds and goes back to the state for its next event, and that
399    // takes the lock, which this holds until the state is closed.
400    let previous_state = pipe.state.with_mut(|state| {
401        pipe.is_disposed.write(true);
402        std::mem::replace(state, State::Closed)
403    });
404    drop(previous_state); // Drop outside the lock to avoid potential deadlock
405}
406
407enum Step<'or, T, E> {
408    /// One more value to deliver.
409    Next(BoxedObserver<'or, T, E>, T),
410    /// The last event of the pipe.
411    Terminate(BoxedObserver<'or, T, E>, Termination<E>),
412    /// Nothing left to deliver: the observer is parked in [`State::Attached`].
413    Park,
414    /// The pipe is over, either before the observer subscribed or by a disposal that happened
415    /// while delivering, so the observer is handed back to be dropped outside the lock.
416    Close(BoxedObserver<'or, T, E>),
417}
418
419/// Delivers the events waiting in [`State::Pending`] to `observer`, one at a time.
420///
421/// This is the replay of the events that were buffered before the subscription, and it is the
422/// whole of what subscribing does: an empty queue simply parks the observer, and a pipe that is
423/// over drops it, so the caller has nothing to check beforehand. The lock is reacquired between
424/// two events, so an event that the sender adds while replaying is delivered in arrival order, and
425/// a disposal takes effect immediately: the loop then drops the observer instead of delivering to
426/// it. The delivery ends by parking the observer into [`State::Attached`], by terminating it, or
427/// by dropping it.
428///
429/// Returns whether the observer was parked, which is the only ending that leaves the pipe alive:
430/// the other two are the pipe being over, which it stays.
431fn deliver<'or, T, E>(
432    pipe: &SharedPipe<'or, T, E>,
433    mut observer: BoxedObserver<'or, T, E>,
434) -> bool {
435    loop {
436        let step = pipe.state.with_mut(|current| {
437            let pending = match &mut *current {
438                State::Pending(pending) => pending,
439                State::Closed => return Step::Close(observer),
440                // This replay holds the observer until it parks it below, so neither the state nor
441                // the sender can be holding it at the same time.
442                State::Attached(_) | State::Held => unreachable!(),
443            };
444            match pending.pop() {
445                Some(Event::Next(value)) => Step::Next(observer, value),
446                Some(Event::Termination(termination)) => {
447                    *current = State::Closed;
448                    Step::Terminate(observer, termination)
449                }
450                None => {
451                    *current = State::Attached(observer);
452                    Step::Park
453                }
454            }
455        });
456        match step {
457            Step::Next(next_observer, value) => {
458                observer = next_observer;
459                // This replay holds the observer on the stack, so a panicking notification unwinds
460                // it away while the state is still `State::Pending`: without the guard, the events
461                // the sender keeps sending would pile up in a queue that nobody drains anymore.
462                // The observer is notified outside the lock, so closing from the guard is safe on
463                // the panicking thread.
464                let close_on_panic = on_panic(|| close(pipe));
465                let flow = observer.on_next(value); // Notify outside the lock
466                drop(close_on_panic);
467                if flow.is_stop() {
468                    // The replayed value ended the stream downstream, so the pipe is over: the
469                    // observer is dropped without being terminated, like a disposed one.
470                    close(pipe);
471                    drop(observer); // Drop outside the lock to avoid potential deadlock
472                    return false;
473                }
474            }
475            Step::Terminate(next_observer, termination) => {
476                // The state was closed under the lock before this step, so a panicking termination
477                // leaves the pipe over already and needs no guard.
478                next_observer.on_termination(termination); // Notify outside the lock
479                return false;
480            }
481            Step::Park => return true,
482            Step::Close(next_observer) => {
483                drop(next_observer); // Drop outside the lock to avoid potential deadlock
484                return false;
485            }
486        }
487    }
488}