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}