mkit_server/http_objects/
paid.rs1use 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#[derive(Clone)]
17pub struct HttpReadRuntime {
18 pub sleep: Arc<dyn Sleep>,
20 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
29pub(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}