Skip to main content

cranpose_services/
async_io.rs

1//! Waking a future from another thread.
2//!
3//! A platform's own I/O is blocking — a synchronous HTTP read, a provider call,
4//! a file descriptor a system API hands over — and the framework has no thread
5//! pool to hide that behind. What it has is the waker: work runs on a thread of
6//! its own and, when it has something, wakes whoever was awaiting it.
7//!
8//! Two shapes cover everything here. A [`Signal`] carries one value, which is
9//! what a request's status line is. A [`ChunkChannel`] carries a stream of byte
10//! chunks with the consumer's progress bounding the producer, which is what a
11//! response body is — without the bound, a slow reader and a fast server put the
12//! whole download in memory, which is the thing streaming exists to avoid.
13
14use std::{
15    collections::VecDeque,
16    future::Future,
17    pin::Pin,
18    sync::{Arc, Condvar, Mutex, PoisonError},
19    task::{Context, Poll, Waker},
20};
21
22/// How many chunks may wait ahead of the consumer before the producer stops
23/// reading. Enough to keep a socket busy across one scheduling gap, and far
24/// short of holding a download in memory.
25pub const MAX_PENDING_CHUNKS: usize = 8;
26
27struct SignalState<T> {
28    value: Option<T>,
29    waker: Option<Waker>,
30    closed: bool,
31}
32
33/// A one-value hand-off between a worker and a future.
34///
35/// The worker calls [`Signal::set`]; whoever awaits [`Signal::wait`] is woken
36/// with it. A worker that dies without setting anything closes the signal, and
37/// the wait resolves to `None` rather than hanging for ever.
38pub struct Signal<T> {
39    state: Arc<Mutex<SignalState<T>>>,
40}
41
42impl<T> Clone for Signal<T> {
43    fn clone(&self) -> Self {
44        Self {
45            state: Arc::clone(&self.state),
46        }
47    }
48}
49
50impl<T> Default for Signal<T> {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl<T> Signal<T> {
57    /// Creates a signal that holds no value yet.
58    pub fn new() -> Self {
59        Self {
60            state: Arc::new(Mutex::new(SignalState {
61                value: None,
62                waker: None,
63                closed: false,
64            })),
65        }
66    }
67
68    /// Delivers the value and wakes the waiter. A second call is ignored: one
69    /// signal carries one value.
70    pub fn set(&self, value: T) {
71        let waker = {
72            let mut state = lock(&self.state);
73            if state.closed {
74                return;
75            }
76            state.value = Some(value);
77            state.closed = true;
78            state.waker.take()
79        };
80        if let Some(waker) = waker {
81            waker.wake();
82        }
83    }
84
85    /// Ends the signal with no value, so a waiter stops waiting.
86    pub fn close(&self) {
87        let waker = {
88            let mut state = lock(&self.state);
89            if state.closed {
90                return;
91            }
92            state.closed = true;
93            state.waker.take()
94        };
95        if let Some(waker) = waker {
96            waker.wake();
97        }
98    }
99
100    /// Resolves with the value, or `None` when the signal was closed empty.
101    pub fn wait(&self) -> SignalWait<T> {
102        SignalWait {
103            state: Arc::clone(&self.state),
104        }
105    }
106}
107
108/// The future [`Signal::wait`] returns.
109pub struct SignalWait<T> {
110    state: Arc<Mutex<SignalState<T>>>,
111}
112
113impl<T> Future for SignalWait<T> {
114    type Output = Option<T>;
115
116    fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<T>> {
117        let mut state = lock(&self.state);
118        if let Some(value) = state.value.take() {
119            return Poll::Ready(Some(value));
120        }
121        if state.closed {
122            return Poll::Ready(None);
123        }
124        state.waker = Some(context.waker().clone());
125        Poll::Pending
126    }
127}
128
129struct ChunkState<E> {
130    ready: VecDeque<Vec<u8>>,
131    error: Option<E>,
132    finished: bool,
133    abandoned: bool,
134    waker: Option<Waker>,
135}
136
137struct ChunkShared<E> {
138    state: Mutex<ChunkState<E>>,
139    room: Condvar,
140}
141
142/// The producing half of a chunked byte stream.
143///
144/// Held by whatever is doing the blocking read. Dropping it without calling
145/// [`ChunkChannel::finish`] or [`ChunkChannel::fail`] ends the stream, so a
146/// worker that panics does not leave a reader waiting for ever.
147pub struct ChunkChannel<E> {
148    shared: Arc<ChunkShared<E>>,
149}
150
151impl<E> ChunkChannel<E> {
152    /// Creates a channel and its reading half.
153    pub fn new() -> (Self, ChunkStream<E>) {
154        let shared = Arc::new(ChunkShared {
155            state: Mutex::new(ChunkState {
156                ready: VecDeque::new(),
157                error: None,
158                finished: false,
159                abandoned: false,
160                waker: None,
161            }),
162            room: Condvar::new(),
163        });
164        (
165            Self {
166                shared: Arc::clone(&shared),
167            },
168            ChunkStream { shared },
169        )
170    }
171
172    /// Publishes one chunk, waiting while the consumer is more than
173    /// [`MAX_PENDING_CHUNKS`] behind.
174    ///
175    /// Returns `false` once the consumer has gone, which is the producer's
176    /// signal to stop reading.
177    pub fn push(&self, chunk: Vec<u8>) -> bool {
178        let waker = {
179            let mut state = lock(&self.shared.state);
180            #[cfg(not(target_arch = "wasm32"))]
181            while state.ready.len() >= MAX_PENDING_CHUNKS && !state.abandoned && !state.finished {
182                state = self
183                    .shared
184                    .room
185                    .wait(state)
186                    .unwrap_or_else(PoisonError::into_inner);
187            }
188            if state.abandoned || state.finished {
189                return false;
190            }
191            state.ready.push_back(chunk);
192            state.waker.take()
193        };
194        if let Some(waker) = waker {
195            waker.wake();
196        }
197        true
198    }
199
200    /// Ends the stream with an error.
201    pub fn fail(&self, error: E) {
202        self.shared.end(Some(error));
203    }
204
205    /// Ends the stream normally.
206    pub fn finish(&self) {
207        self.shared.end(None);
208    }
209
210    /// Whether the consumer has gone.
211    pub fn is_abandoned(&self) -> bool {
212        lock(&self.shared.state).abandoned
213    }
214}
215
216impl<E> Drop for ChunkChannel<E> {
217    fn drop(&mut self) {
218        self.finish();
219    }
220}
221
222impl<E> ChunkShared<E> {
223    fn end(&self, error: Option<E>) {
224        let waker = {
225            let mut state = lock(&self.state);
226            if state.finished {
227                return;
228            }
229            state.error = error;
230            state.finished = true;
231            state.waker.take()
232        };
233        self.room.notify_all();
234        if let Some(waker) = waker {
235            waker.wake();
236        }
237    }
238}
239
240/// The consuming half of a chunked byte stream.
241pub struct ChunkStream<E> {
242    shared: Arc<ChunkShared<E>>,
243}
244
245impl<E> ChunkStream<E> {
246    /// Resolves with the next chunk, `Ok(None)` at the end of the stream, or
247    /// the error the producer ended with.
248    pub fn next(&self) -> ChunkNext<'_, E> {
249        ChunkNext { stream: self }
250    }
251
252    /// Ends the stream from the reading side: the next read resolves with
253    /// `error` once the chunks already here are taken, and the producer's
254    /// next push returns `false`. Ignored when the stream has already ended.
255    pub fn fail(&self, error: E) {
256        self.shared.end(Some(error));
257    }
258}
259
260impl<E> Drop for ChunkStream<E> {
261    fn drop(&mut self) {
262        let mut state = lock(&self.shared.state);
263        state.abandoned = true;
264        drop(state);
265        self.shared.room.notify_all();
266    }
267}
268
269/// The future [`ChunkStream::next`] returns.
270pub struct ChunkNext<'a, E> {
271    stream: &'a ChunkStream<E>,
272}
273
274impl<E> Future for ChunkNext<'_, E> {
275    type Output = Result<Option<Vec<u8>>, E>;
276
277    fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
278        let shared = &self.stream.shared;
279        let mut state = lock(&shared.state);
280        if let Some(chunk) = state.ready.pop_front() {
281            drop(state);
282            shared.room.notify_one();
283            return Poll::Ready(Ok(Some(chunk)));
284        }
285        if let Some(error) = state.error.take() {
286            return Poll::Ready(Err(error));
287        }
288        if state.finished {
289            return Poll::Ready(Ok(None));
290        }
291        state.waker = Some(context.waker().clone());
292        Poll::Pending
293    }
294}
295
296fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
297    mutex.lock().unwrap_or_else(PoisonError::into_inner)
298}
299
300#[cfg(test)]
301#[path = "tests/async_io_tests.rs"]
302mod tests;