cranpose_services/
async_io.rs1use std::collections::VecDeque;
15use std::future::Future;
16use std::pin::Pin;
17use std::sync::{Arc, Condvar, Mutex};
18use std::task::{Context, Poll, Waker};
19
20pub const MAX_PENDING_CHUNKS: usize = 8;
24
25struct SignalState<T> {
26 value: Option<T>,
27 waker: Option<Waker>,
28 closed: bool,
29}
30
31pub struct Signal<T> {
37 state: Arc<Mutex<SignalState<T>>>,
38}
39
40impl<T> Clone for Signal<T> {
41 fn clone(&self) -> Self {
42 Self {
43 state: Arc::clone(&self.state),
44 }
45 }
46}
47
48impl<T> Default for Signal<T> {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl<T> Signal<T> {
55 pub fn new() -> Self {
56 Self {
57 state: Arc::new(Mutex::new(SignalState {
58 value: None,
59 waker: None,
60 closed: false,
61 })),
62 }
63 }
64
65 pub fn set(&self, value: T) {
68 let waker = {
69 let mut state = lock(&self.state);
70 if state.closed {
71 return;
72 }
73 state.value = Some(value);
74 state.closed = true;
75 state.waker.take()
76 };
77 if let Some(waker) = waker {
78 waker.wake();
79 }
80 }
81
82 pub fn close(&self) {
84 let waker = {
85 let mut state = lock(&self.state);
86 if state.closed {
87 return;
88 }
89 state.closed = true;
90 state.waker.take()
91 };
92 if let Some(waker) = waker {
93 waker.wake();
94 }
95 }
96
97 pub fn wait(&self) -> SignalWait<T> {
99 SignalWait {
100 state: Arc::clone(&self.state),
101 }
102 }
103}
104
105pub struct SignalWait<T> {
107 state: Arc<Mutex<SignalState<T>>>,
108}
109
110impl<T> Future for SignalWait<T> {
111 type Output = Option<T>;
112
113 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<T>> {
114 let mut state = lock(&self.state);
115 if let Some(value) = state.value.take() {
116 return Poll::Ready(Some(value));
117 }
118 if state.closed {
119 return Poll::Ready(None);
120 }
121 state.waker = Some(context.waker().clone());
122 Poll::Pending
123 }
124}
125
126struct ChunkState<E> {
127 ready: VecDeque<Vec<u8>>,
128 error: Option<E>,
129 finished: bool,
130 abandoned: bool,
133 waker: Option<Waker>,
134}
135
136struct ChunkShared<E> {
137 state: Mutex<ChunkState<E>>,
138 room: Condvar,
140}
141
142pub struct ChunkChannel<E> {
148 shared: Arc<ChunkShared<E>>,
149}
150
151impl<E> ChunkChannel<E> {
152 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 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"))]
183 while state.ready.len() >= MAX_PENDING_CHUNKS && !state.abandoned {
184 state = self
185 .shared
186 .room
187 .wait(state)
188 .unwrap_or_else(|error| error.into_inner());
189 }
190 if state.abandoned || state.finished {
191 return false;
192 }
193 state.ready.push_back(chunk);
194 state.waker.take()
195 };
196 if let Some(waker) = waker {
197 waker.wake();
198 }
199 true
200 }
201
202 pub fn fail(&self, error: E) {
204 let waker = {
205 let mut state = lock(&self.shared.state);
206 if state.finished {
207 return;
208 }
209 state.error = Some(error);
210 state.finished = true;
211 state.waker.take()
212 };
213 if let Some(waker) = waker {
214 waker.wake();
215 }
216 }
217
218 pub fn finish(&self) {
220 let waker = {
221 let mut state = lock(&self.shared.state);
222 if state.finished {
223 return;
224 }
225 state.finished = true;
226 state.waker.take()
227 };
228 if let Some(waker) = waker {
229 waker.wake();
230 }
231 }
232
233 pub fn is_abandoned(&self) -> bool {
235 lock(&self.shared.state).abandoned
236 }
237}
238
239impl<E> Drop for ChunkChannel<E> {
240 fn drop(&mut self) {
241 self.finish();
242 }
243}
244
245pub struct ChunkStream<E> {
247 shared: Arc<ChunkShared<E>>,
248}
249
250impl<E> ChunkStream<E> {
251 pub fn next(&self) -> ChunkNext<'_, E> {
254 ChunkNext { stream: self }
255 }
256}
257
258impl<E> Drop for ChunkStream<E> {
259 fn drop(&mut self) {
260 let mut state = lock(&self.shared.state);
261 state.abandoned = true;
262 drop(state);
263 self.shared.room.notify_all();
265 }
266}
267
268pub struct ChunkNext<'a, E> {
270 stream: &'a ChunkStream<E>,
271}
272
273impl<E> Future for ChunkNext<'_, E> {
274 type Output = Result<Option<Vec<u8>>, E>;
275
276 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
277 let shared = Arc::clone(&self.stream.shared);
278 let mut state = lock(&shared.state);
279 if let Some(chunk) = state.ready.pop_front() {
280 drop(state);
281 shared.room.notify_one();
282 return Poll::Ready(Ok(Some(chunk)));
283 }
284 if let Some(error) = state.error.take() {
285 return Poll::Ready(Err(error));
286 }
287 if state.finished {
288 return Poll::Ready(Ok(None));
289 }
290 state.waker = Some(context.waker().clone());
291 Poll::Pending
292 }
293}
294
295fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
296 mutex.lock().unwrap_or_else(|error| error.into_inner())
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[derive(Debug, PartialEq, Eq)]
304 struct Failed(&'static str);
305
306 #[test]
307 fn a_signal_carries_one_value_to_whoever_waits() {
308 let signal = Signal::new();
309 signal.set(7u32);
310 assert_eq!(pollster::block_on(signal.wait()), Some(7));
311 }
312
313 #[test]
315 fn a_closed_signal_resolves_to_nothing_rather_than_waiting_for_ever() {
316 let signal = Signal::<u32>::new();
317 signal.close();
318 assert_eq!(pollster::block_on(signal.wait()), None);
319 }
320
321 #[test]
322 fn a_signal_set_from_another_thread_wakes_the_waiter() {
323 let signal = Signal::new();
324 let worker = signal.clone();
325 let handle = std::thread::spawn(move || {
326 std::thread::sleep(std::time::Duration::from_millis(20));
327 worker.set(11u32);
328 });
329 assert_eq!(pollster::block_on(signal.wait()), Some(11));
330 handle.join().expect("the worker finishes");
331 }
332
333 #[test]
334 fn chunks_arrive_in_the_order_they_were_produced() {
335 let (channel, stream) = ChunkChannel::<Failed>::new();
336 assert!(channel.push(b"one".to_vec()));
337 assert!(channel.push(b"two".to_vec()));
338 channel.finish();
339
340 assert_eq!(pollster::block_on(stream.next()), Ok(Some(b"one".to_vec())));
341 assert_eq!(pollster::block_on(stream.next()), Ok(Some(b"two".to_vec())));
342 assert_eq!(pollster::block_on(stream.next()), Ok(None));
343 }
344
345 #[test]
346 fn a_failed_stream_reports_the_error_after_what_it_already_produced() {
347 let (channel, stream) = ChunkChannel::new();
348 assert!(channel.push(b"partial".to_vec()));
349 channel.fail(Failed("the connection dropped"));
350
351 assert_eq!(
352 pollster::block_on(stream.next()),
353 Ok(Some(b"partial".to_vec()))
354 );
355 assert_eq!(
356 pollster::block_on(stream.next()),
357 Err(Failed("the connection dropped"))
358 );
359 }
360
361 #[test]
364 fn dropping_the_producer_ends_the_stream() {
365 let (channel, stream) = ChunkChannel::<Failed>::new();
366 drop(channel);
367 assert_eq!(pollster::block_on(stream.next()), Ok(None));
368 }
369
370 #[test]
373 fn the_producer_waits_while_the_consumer_is_behind() {
374 let (channel, stream) = ChunkChannel::<Failed>::new();
375 let pushed = Arc::new(std::sync::atomic::AtomicUsize::new(0));
376 let counter = Arc::clone(&pushed);
377 let worker = std::thread::spawn(move || {
378 for index in 0..MAX_PENDING_CHUNKS + 4 {
379 if !channel.push(vec![index as u8]) {
380 break;
381 }
382 counter.fetch_add(1, std::sync::atomic::Ordering::Release);
383 }
384 channel.finish();
385 });
386
387 std::thread::sleep(std::time::Duration::from_millis(50));
389 assert!(
390 pushed.load(std::sync::atomic::Ordering::Acquire) <= MAX_PENDING_CHUNKS,
391 "the producer must stop at the bound rather than reading ahead without limit"
392 );
393
394 let mut received = 0usize;
395 while let Ok(Some(_)) = pollster::block_on(stream.next()) {
396 received += 1;
397 }
398 assert_eq!(received, MAX_PENDING_CHUNKS + 4);
399 worker.join().expect("the worker finishes");
400 }
401
402 #[test]
406 fn abandoning_the_stream_stops_the_producer() {
407 let (channel, stream) = ChunkChannel::<Failed>::new();
408 assert!(channel.push(b"first".to_vec()));
409 drop(stream);
410 assert!(channel.is_abandoned());
411 assert!(
412 !channel.push(b"second".to_vec()),
413 "a push after the consumer has gone must report that nobody is reading"
414 );
415 }
416}