yash_executor/forwarder.rs
1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2024 WATANABE Yuki
3
4//! Utilities for forwarding the result of a future to another future
5//!
6//! The [`forwarder`] function creates a pair of [`Sender`] and [`Receiver`] that
7//! can be used to forward the result of a future to another future. The sender
8//! half is used to send the result, and the receiver half is used to receive the
9//! result.
10//!
11//! ```
12//! # use yash_executor::forwarder::*;
13//! let (sender, receiver) = forwarder::<u32>();
14//!
15//! // The result is not yet available
16//! assert_eq!(receiver.try_receive(), Err(TryReceiveError::NotSent));
17//!
18//! // Send the result
19//! sender.send(42).unwrap();
20//!
21//! // The result is now available
22//! assert_eq!(receiver.try_receive(), Ok(42));
23//! ```
24//!
25//! If the `Sender` is dropped before sending the result, the `Receiver` will
26//! never receive the result. If the `Receiver` is dropped before receiving the
27//! result, the `Sender` will not be able to send the result, but it does not
28//! otherwise affect the task that produces the result.
29
30use alloc::rc::{Rc, Weak};
31use core::cell::RefCell;
32use core::fmt::Display;
33use core::pin::Pin;
34use core::task::{Context, Poll, Waker};
35
36/// State shared between the sender and receiver
37#[derive(Debug)]
38enum Relay<T> {
39 /// The result has not been computed yet, and the receiver has not been polled.
40 Pending,
41 /// The result has not been computed yet, and the receiver has been polled.
42 Polled(Waker),
43 /// The result has been computed, but the receiver has not received it yet.
44 Computed(T),
45 /// The receiver has received the result.
46 Done,
47}
48
49/// Sender half of the forwarder
50///
51/// See the [module-level documentation](self) for more information.
52#[derive(Debug)]
53pub struct Sender<T> {
54 relay: Weak<RefCell<Relay<T>>>,
55}
56
57/// Receiver half of the forwarder
58///
59/// Call [`try_receive`](Self::try_receive) to examine if the result has been
60/// sent from the sender. `Receiver` also implements the `Future` trait, so you
61/// can use it in an async block or function to receive the result
62/// asynchronously.
63///
64/// See also the [module-level documentation](self) for more information.
65#[derive(Debug)]
66pub struct Receiver<T> {
67 relay: Rc<RefCell<Relay<T>>>,
68}
69
70/// Creates a new forwarder.
71#[must_use]
72pub fn forwarder<T>() -> (Sender<T>, Receiver<T>) {
73 let relay = Rc::new(RefCell::new(Relay::Pending));
74 let sender = Sender {
75 relay: Rc::downgrade(&relay),
76 };
77 let receiver = Receiver { relay };
78 (sender, receiver)
79}
80
81/// Error returned when receiving a value fails
82///
83/// This error may be returned from the [`Receiver::try_receive`] method.
84#[derive(Clone, Copy, Debug, Eq, PartialEq)]
85pub enum TryReceiveError {
86 /// The sender has been dropped, which means the receiver will never receive
87 /// the value.
88 SenderDropped,
89 /// The value has not been sent yet.
90 NotSent,
91 /// The value has already been received.
92 AlreadyReceived,
93}
94
95impl<T> Sender<T> {
96 /// Sends a value to the receiver.
97 ///
98 /// The value is sent to the receiver. If the receiver has been dropped,
99 /// the value is returned back to the caller.
100 ///
101 /// This method consumes the sender, ensuring that the value is sent at most
102 /// once for each sender-receiver pair.
103 pub fn send(self, value: T) -> Result<(), T> {
104 let Some(relay) = self.relay.upgrade() else {
105 return Err(value);
106 };
107
108 let relay = &mut *relay.borrow_mut();
109 match core::mem::replace(relay, Relay::Computed(value)) {
110 Relay::Pending => Ok(()),
111 Relay::Polled(waker) => {
112 waker.wake();
113 Ok(())
114 }
115 // We can send only once, so these cases are impossible
116 Relay::Computed(_) | Relay::Done => unreachable!(),
117 }
118 }
119}
120
121impl<T> Receiver<T> {
122 /// Receives a value from the sender.
123 ///
124 /// This method is similar to [`poll`](Self::poll), but it does not require
125 /// a `Context` argument. If the value has not been sent yet, this method
126 /// returns `Err(TryReceiveError::NotSent)`.
127 pub fn try_receive(&self) -> Result<T, TryReceiveError> {
128 let relay = &mut *self.relay.borrow_mut();
129 match relay {
130 Relay::Pending | Relay::Polled(_) => {
131 if Rc::weak_count(&self.relay) == 0 {
132 Err(TryReceiveError::SenderDropped)
133 } else {
134 Err(TryReceiveError::NotSent)
135 }
136 }
137
138 Relay::Computed(_) => {
139 let Relay::Computed(value) = core::mem::replace(relay, Relay::Done) else {
140 unreachable!()
141 };
142 Ok(value)
143 }
144
145 Relay::Done => Err(TryReceiveError::AlreadyReceived),
146 }
147 }
148}
149
150impl<T> Future for Receiver<T> {
151 type Output = T;
152
153 /// Polls the receiver to receive the value.
154 ///
155 /// This method is similar to [`try_receive`](Self::try_receive), but it
156 /// requires a `Context` argument. If the value has not been sent yet, this
157 /// method returns `Poll::Pending` and stores the `Waker` from the `Context`
158 /// for waking up the current task when the value is sent.
159 ///
160 /// This method should not be called after the value has been received.
161 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<T> {
162 let relay = &mut *self.relay.borrow_mut();
163 match relay {
164 Relay::Pending | Relay::Polled(_) => {
165 *relay = Relay::Polled(context.waker().clone());
166 Poll::Pending
167 }
168
169 Relay::Computed(_) => {
170 let Relay::Computed(value) = core::mem::replace(relay, Relay::Done) else {
171 unreachable!()
172 };
173 Poll::Ready(value)
174 }
175
176 Relay::Done => panic!("Receiver polled after receiving the value"),
177 }
178 }
179}
180
181impl Display for TryReceiveError {
182 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
183 match self {
184 TryReceiveError::SenderDropped => "sender already dropped".fmt(f),
185 TryReceiveError::NotSent => "result not sent yet".fmt(f),
186 TryReceiveError::AlreadyReceived => "result already received".fmt(f),
187 }
188 }
189}
190
191// TODO Bump MSRV to 1.81.0 to impl core::error::Error for TryReceiveError