Skip to main content

mkit_server/http_objects/
paid.rs

1//! Paid body accounting. Bytes mean bytes handed to the response stream.
2use core::pin::Pin;
3use core::task::{Context, Poll};
4use std::sync::Arc;
5
6use bytes::Bytes;
7use futures::{Stream, StreamExt};
8
9use super::HttpBody;
10use crate::rt::{BoxFuture, Clock, Sleep, Spawner};
11use crate::{BoxStream, ServerError};
12
13/// Runtime services required to retain read settlement after cancellation.
14/// Native adapters use tasks; Worker adapters must use the request's
15/// `wait_until` lifetime. A spawner must run accepted work to completion.
16#[derive(Clone)]
17pub struct HttpReadRuntime {
18    /// Timer used to stop transmission even while the source is pending.
19    pub sleep: Arc<dyn Sleep>,
20    /// Retains settlement independently of the response body's lifetime.
21    pub spawner: Arc<dyn Spawner>,
22}
23
24#[cfg(not(target_arch = "wasm32"))]
25type Finish = Box<dyn FnOnce(u64, bool) -> BoxFuture<'static, ()> + Send>;
26#[cfg(target_arch = "wasm32")]
27type Finish = Box<dyn FnOnce(u64, bool) -> BoxFuture<'static, ()>>;
28
29/// Owns the obligation as soon as Pending is durable, including while the
30/// byte source is being opened. Dropping it retains a zero-byte abort.
31pub(crate) struct ReadFinalizer {
32    pub(crate) deadline_ms: u64,
33    pub(crate) clock: Arc<dyn Clock>,
34    pub(crate) runtime: HttpReadRuntime,
35    pub(crate) finish: Option<Finish>,
36    pub(crate) sent: u64,
37}
38
39impl core::fmt::Debug for HttpReadRuntime {
40    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41        f.debug_struct("HttpReadRuntime").finish_non_exhaustive()
42    }
43}
44
45impl ReadFinalizer {
46    fn start(&mut self, success: bool) -> BoxFuture<'static, ()> {
47        let Some(finish) = self.finish.take() else {
48            return Box::pin(async {});
49        };
50        let work = finish(self.sent, success);
51        let (tx, rx) = tokio::sync::oneshot::channel();
52        self.runtime.spawner.spawn(Box::pin(async move {
53            work.await;
54            let _ = tx.send(());
55        }));
56        Box::pin(async move {
57            let _ = rx.await;
58        })
59    }
60
61    pub(crate) async fn complete(mut self, success: bool) {
62        self.start(success).await;
63    }
64
65    pub(crate) fn wrap(self, body: HttpBody) -> HttpBody {
66        let (len, source): (u64, BoxStream<'static, Result<Bytes, ServerError>>) = match body {
67            HttpBody::Empty => (0, Box::pin(futures::stream::empty())),
68            HttpBody::Bytes(bytes) => (
69                bytes.len() as u64,
70                Box::pin(futures::stream::once(core::future::ready(Ok(bytes)))),
71            ),
72            HttpBody::Stream { len, stream } => (len, stream),
73        };
74        let now = u64::try_from(self.clock.now_ms()).unwrap_or(0);
75        let timer = self.runtime.sleep.sleep(core::time::Duration::from_millis(
76            self.deadline_ms.saturating_sub(now),
77        ));
78        HttpBody::Stream {
79            len,
80            stream: Box::pin(Paid {
81                source,
82                finalizer: self,
83                timer,
84                settling: None,
85                error: None,
86                done: false,
87            }),
88        }
89    }
90}
91
92impl Drop for ReadFinalizer {
93    fn drop(&mut self) {
94        drop(self.start(false));
95    }
96}
97
98struct Paid {
99    source: BoxStream<'static, Result<Bytes, ServerError>>,
100    finalizer: ReadFinalizer,
101    timer: BoxFuture<'static, ()>,
102    settling: Option<BoxFuture<'static, ()>>,
103    error: Option<ServerError>,
104    done: bool,
105}
106
107impl Stream for Paid {
108    type Item = Result<Bytes, ServerError>;
109    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
110        if self.done {
111            return Poll::Ready(None);
112        }
113        if self.settling.is_none() {
114            let elapsed = u64::try_from(self.finalizer.clock.now_ms()).unwrap_or(0)
115                >= self.finalizer.deadline_ms
116                || self.timer.as_mut().poll(cx).is_ready();
117            let result = if elapsed {
118                Poll::Ready(Some(Err(ServerError::unavailable("read deadline passed"))))
119            } else {
120                self.source.poll_next_unpin(cx)
121            };
122            match result {
123                Poll::Pending => return Poll::Pending,
124                Poll::Ready(Some(Ok(_)))
125                    if u64::try_from(self.finalizer.clock.now_ms()).unwrap_or(0)
126                        >= self.finalizer.deadline_ms =>
127                {
128                    self.error = Some(ServerError::unavailable("read deadline passed"));
129                    self.settling = Some(self.finalizer.start(false));
130                }
131                Poll::Ready(Some(Ok(piece))) => {
132                    self.finalizer.sent += piece.len() as u64;
133                    return Poll::Ready(Some(Ok(piece)));
134                }
135                Poll::Ready(Some(Err(error))) => {
136                    self.error = Some(error);
137                    self.settling = Some(self.finalizer.start(false));
138                }
139                Poll::Ready(None) => self.settling = Some(self.finalizer.start(true)),
140            }
141        }
142        if let Some(settling) = &mut self.settling
143            && settling.as_mut().poll(cx).is_pending()
144        {
145            return Poll::Pending;
146        }
147        self.done = true;
148        Poll::Ready(self.error.take().map(Err))
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    #![allow(clippy::unwrap_used)]
155    use super::*;
156    use crate::rt::{ManualClock, ManualSleep};
157    use std::sync::Mutex;
158
159    #[derive(Default)]
160    struct Queued(Mutex<Vec<BoxFuture<'static, ()>>>);
161    impl Spawner for Queued {
162        fn spawn(&self, fut: BoxFuture<'static, ()>) {
163            self.0.lock().unwrap().push(fut);
164        }
165    }
166    impl Queued {
167        fn run(&self) {
168            for task in self.0.lock().unwrap().drain(..) {
169                futures_executor::block_on(task);
170            }
171        }
172    }
173    type Ends = Arc<Mutex<Vec<(u64, bool)>>>;
174    fn finalizer() -> (ReadFinalizer, Arc<Queued>, Arc<ManualSleep>, Ends) {
175        let tasks = Arc::new(Queued::default());
176        let sleep = Arc::new(ManualSleep::new());
177        let ended: Ends = Arc::default();
178        let save = ended.clone();
179        (
180            ReadFinalizer {
181                deadline_ms: 1000,
182                clock: Arc::new(ManualClock::new(0)),
183                runtime: HttpReadRuntime {
184                    sleep: sleep.clone(),
185                    spawner: tasks.clone(),
186                },
187                sent: 0,
188                finish: Some(Box::new(move |n, success| {
189                    Box::pin(async move {
190                        save.lock().unwrap().push((n, success));
191                    })
192                })),
193            },
194            tasks,
195            sleep,
196            ended,
197        )
198    }
199    fn poll(
200        stream: &mut BoxStream<'static, Result<Bytes, ServerError>>,
201    ) -> Poll<Option<Result<Bytes, ServerError>>> {
202        stream
203            .as_mut()
204            .poll_next(&mut Context::from_waker(futures::task::noop_waker_ref()))
205    }
206
207    #[test]
208    fn normal_eof_waits_for_finalizer_and_drop_while_waiting_retains_it() {
209        for cancel in [false, true] {
210            let (f, tasks, _, ended) = finalizer();
211            let HttpBody::Stream { mut stream, .. } =
212                f.wrap(HttpBody::Bytes(Bytes::from_static(b"abc")))
213            else {
214                panic!()
215            };
216            assert!(matches!(poll(&mut stream), Poll::Ready(Some(Ok(_)))));
217            assert!(poll(&mut stream).is_pending());
218            assert!(ended.lock().unwrap().is_empty());
219            if cancel {
220                drop(stream);
221                tasks.run();
222            } else {
223                tasks.run();
224                assert!(matches!(poll(&mut stream), Poll::Ready(None)));
225            }
226            assert_eq!(*ended.lock().unwrap(), [(3, true)]);
227        }
228    }
229    #[test]
230    fn deadline_wakes_pending_source_and_aborts_zero_byte_read() {
231        let (f, tasks, sleep, ended) = finalizer();
232        let HttpBody::Stream { mut stream, .. } = f.wrap(HttpBody::Stream {
233            len: 1,
234            stream: Box::pin(futures::stream::pending()),
235        }) else {
236            panic!()
237        };
238        assert!(poll(&mut stream).is_pending());
239        sleep.fire();
240        assert!(
241            poll(&mut stream).is_pending(),
242            "settlement precedes the stream error"
243        );
244        tasks.run();
245        assert!(matches!(poll(&mut stream), Poll::Ready(Some(Err(_)))));
246        assert_eq!(*ended.lock().unwrap(), [(0, false)]);
247    }
248    #[test]
249    fn a_backend_poll_cannot_hand_out_a_piece_after_the_deadline() {
250        let (mut f, tasks, _, ended) = finalizer();
251        let clock = Arc::new(ManualClock::new(0));
252        f.clock = clock.clone();
253        let source = futures::stream::once(async move {
254            clock.set(1000);
255            Ok(Bytes::from_static(b"x"))
256        });
257        let HttpBody::Stream { mut stream, .. } = f.wrap(HttpBody::Stream {
258            len: 1,
259            stream: Box::pin(source),
260        }) else {
261            panic!()
262        };
263        assert!(poll(&mut stream).is_pending());
264        tasks.run();
265        assert!(matches!(poll(&mut stream), Poll::Ready(Some(Err(_)))));
266        assert_eq!(*ended.lock().unwrap(), [(0, false)]);
267    }
268
269    #[test]
270    fn zero_length_get_settles_successfully() {
271        let (f, tasks, _, ended) = finalizer();
272        let HttpBody::Stream { mut stream, .. } = f.wrap(HttpBody::Empty) else {
273            panic!()
274        };
275        assert!(poll(&mut stream).is_pending());
276        tasks.run();
277        assert!(matches!(poll(&mut stream), Poll::Ready(None)));
278        assert_eq!(*ended.lock().unwrap(), [(0, true)]);
279    }
280}