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