Skip to main content

base_http/body/
writable.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use super::{BodyError, BodyFrame, ReadableBody, WritableBody};
5use crate::future::cancellable;
6use base_scheduler::Cancellation;
7use event_listener::Event;
8use std::{
9    collections::VecDeque,
10    sync::{Arc, Mutex, MutexGuard, PoisonError},
11};
12
13struct State<D> {
14    frames: VecDeque<(BodyFrame<D>, usize)>,
15    bytes: usize,
16    read_closed: bool,
17    write_closed: bool,
18    headers: bool,
19    error: Option<BodyError>,
20}
21
22pub(crate) struct Shared<D> {
23    state: Mutex<State<D>>,
24    readable: Event,
25    writable: Event,
26    max_bytes: usize,
27    max_chunks: usize,
28    length: Option<u64>,
29}
30
31impl<D> Shared<D> {
32    fn lock(&self) -> MutexGuard<'_, State<D>> {
33        self.state.lock().unwrap_or_else(PoisonError::into_inner)
34    }
35
36    // The unique writer calls this at most once, from `close_write` or its drop, and never after accepting a terminal frame, so output is still open here.
37    pub(crate) fn finish(&self, mut error: Option<BodyError>) {
38        let discarded = {
39            let mut state = self.lock();
40            let discarded = if error.is_some() {
41                if state.error.is_none() {
42                    state.error = error.take();
43                }
44                state.bytes = 0;
45                std::mem::take(&mut state.frames)
46            } else {
47                VecDeque::new()
48            };
49            state.write_closed = true;
50            discarded
51        };
52        self.readable.notify(usize::MAX);
53        self.writable.notify(usize::MAX);
54        drop(discarded);
55    }
56}
57
58/// The readable half of a bounded, single-producer body.
59///
60/// Byte limits count data and field name/value bytes; item limits count every
61/// frame, including empty data and headers. Oversized frames are rejected rather
62/// than waiting forever. Owners may retain additional storage. Dropping/closing
63/// the reader abandons queued frames and wakes the writer. A temporarily empty
64/// open body is neither ready nor exhausted.
65pub struct BodyReader<D = Vec<u8>> {
66    shared: Arc<Shared<D>>,
67}
68
69/// The unique output half of a [`BodyReader`] body.
70///
71/// Finish with a terminal frame or `close_write(None)` to preserve queued frames.
72/// An error closes immediately and discards queued frames. Dropping unfinished
73/// output reports abandonment. A cancelled/pending write has not queued its
74/// frame; the supplied owner is dropped when that write future is dropped.
75pub struct BodyWriter<D = Vec<u8>> {
76    shared: Arc<Shared<D>>,
77    closed: bool,
78}
79
80impl<D: AsRef<[u8]> + Send> BodyReader<D> {
81    /// Create independently movable input/output halves. Panics for zero limits.
82    pub fn bounded(max_bytes: usize, max_chunks: usize) -> (Self, BodyWriter<D>) {
83        Self::pair(max_bytes, max_chunks, None)
84    }
85
86    /// Declare total representation length; protocol framing checks actual size.
87    pub fn with_length(max_bytes: usize, max_chunks: usize, length: u64) -> (Self, BodyWriter<D>) {
88        Self::pair(max_bytes, max_chunks, Some(length))
89    }
90
91    fn pair(max_bytes: usize, max_chunks: usize, length: Option<u64>) -> (Self, BodyWriter<D>) {
92        assert!(max_bytes > 0, "body byte limit must be positive");
93        assert!(max_chunks > 0, "body chunk limit must be positive");
94        let shared = Arc::new(Shared {
95            state: Mutex::new(State {
96                frames: VecDeque::new(),
97                bytes: 0,
98                read_closed: false,
99                write_closed: false,
100                headers: false,
101                error: None,
102            }),
103            readable: Event::new(),
104            writable: Event::new(),
105            max_bytes,
106            max_chunks,
107            length,
108        });
109        (
110            Self {
111                shared: Arc::clone(&shared),
112            },
113            BodyWriter {
114                shared,
115                closed: false,
116            },
117        )
118    }
119}
120
121impl<D: AsRef<[u8]> + Send> ReadableBody for BodyReader<D> {
122    type Data = D;
123    type Error = BodyError;
124
125    fn is_ready(&self) -> bool {
126        let state = self.shared.lock();
127        !state.frames.is_empty() || state.read_closed || state.write_closed
128    }
129    fn is_empty(&self) -> bool {
130        let state = self.shared.lock();
131        state.frames.is_empty() && (state.read_closed || state.write_closed)
132    }
133    fn length(&self) -> Option<u64> {
134        self.shared.length
135    }
136    fn close(&mut self, error: Option<BodyError>) {
137        let discarded = {
138            let mut state = self.shared.lock();
139            state.read_closed = true;
140            state.bytes = 0;
141            if state.error.is_none() {
142                state.error = error;
143            }
144            std::mem::take(&mut state.frames)
145        };
146        self.shared.readable.notify(usize::MAX);
147        self.shared.writable.notify(usize::MAX);
148        drop(discarded);
149    }
150
151    async fn read(&mut self, cancellation: &Cancellation) -> Result<BodyFrame<D>, BodyError> {
152        loop {
153            cancellation.check()?;
154            // Register before examining the condition to avoid lost wakeups.
155            event_listener::listener!(self.shared.readable => listener);
156            let chunk = {
157                let mut state = self.shared.lock();
158                if let Some(error) = &state.error {
159                    return Err(error.clone());
160                }
161                if let Some((mut frame, length)) = state.frames.pop_front() {
162                    state.bytes -= length;
163                    if state.write_closed && state.frames.is_empty() {
164                        match &mut frame {
165                            BodyFrame::Data { end, .. } | BodyFrame::Headers { end, .. } => {
166                                *end = true
167                            }
168                            BodyFrame::End => {}
169                        }
170                    }
171                    Some(frame)
172                } else if state.write_closed || state.read_closed {
173                    return Ok(BodyFrame::End);
174                } else {
175                    None
176                }
177            };
178            if let Some(chunk) = chunk {
179                self.shared.writable.notify(usize::MAX);
180                return Ok(chunk);
181            }
182            cancellable(listener, cancellation).await?;
183        }
184    }
185}
186
187impl<D: AsRef<[u8]> + Send> BodyWriter<D> {
188    pub async fn write(&mut self, data: D, cancellation: &Cancellation) -> Result<(), BodyError> {
189        self.write_frame(BodyFrame::Data { data, end: false }, cancellation)
190            .await
191    }
192
193    /// Move a whole frame into the queue. Headers must follow all data; an end
194    /// flag or `End` closes output atomically with accepting that frame.
195    /// Capacity counts data bytes and field name/value bytes, plus every frame
196    /// against the item limit. As with data, owners may retain additional memory.
197    pub async fn write_frame(
198        &mut self,
199        frame: BodyFrame<D>,
200        cancellation: &Cancellation,
201    ) -> Result<(), BodyError> {
202        cancellation.check()?;
203        let length = match &frame {
204            BodyFrame::Data { data, .. } => data.as_ref().len(),
205            BodyFrame::Headers { fields, .. } => {
206                fields.pairs().iter().fold(0usize, |sum, field| {
207                    sum.saturating_add(field.name().len())
208                        .saturating_add(field.value().len())
209                })
210            }
211            BodyFrame::End => 0,
212        };
213        if length > self.shared.max_bytes {
214            return Err(BodyError::ChunkTooLarge {
215                length,
216                limit: self.shared.max_bytes,
217            });
218        }
219        loop {
220            event_listener::listener!(self.shared.writable => listener);
221            {
222                let mut state = self.shared.lock();
223                if let Some(error) = &state.error {
224                    return Err(error.clone());
225                }
226                if state.read_closed {
227                    return Err(BodyError::Abandoned);
228                }
229                if state.write_closed {
230                    return Err(BodyError::Closed);
231                }
232                if state.headers && !matches!(&frame, BodyFrame::End) {
233                    return Err(BodyError::InvalidFrame);
234                }
235                if state.frames.len() < self.shared.max_chunks
236                    && state.bytes <= self.shared.max_bytes - length
237                {
238                    state.headers |= matches!(&frame, BodyFrame::Headers { .. });
239                    let end = frame.is_end();
240                    state.bytes += length;
241                    state.frames.push_back((frame, length));
242                    if end {
243                        state.write_closed = true;
244                        self.closed = true;
245                    }
246                    drop(state);
247                    self.shared.readable.notify(usize::MAX);
248                    return Ok(());
249                }
250            }
251            cancellable(listener, cancellation).await?;
252        }
253    }
254
255    pub async fn flush(&mut self, cancellation: &Cancellation) -> Result<(), BodyError> {
256        cancellation.check()?;
257        let state = self.shared.lock();
258        if let Some(error) = &state.error {
259            return Err(error.clone());
260        }
261        if state.read_closed {
262            return Err(BodyError::Abandoned);
263        }
264        // Queued chunks are already visible to the consumer. The protocol
265        // writer, rather than this queue, owns transport flushing.
266        Ok(())
267    }
268
269    pub fn close_write(&mut self, error: Option<BodyError>) {
270        if !self.closed {
271            self.closed = true;
272            self.shared.finish(error);
273        }
274    }
275
276    pub fn abort(&mut self) {
277        self.close_write(Some(BodyError::Abandoned));
278    }
279}
280
281impl<D> Drop for BodyReader<D> {
282    fn drop(&mut self) {
283        let discarded = {
284            let mut state = self.shared.lock();
285            state.read_closed = true;
286            state.bytes = 0;
287            std::mem::take(&mut state.frames)
288        };
289        self.shared.writable.notify(usize::MAX);
290        drop(discarded);
291    }
292}
293
294impl<D> Drop for BodyWriter<D> {
295    fn drop(&mut self) {
296        if !self.closed {
297            self.shared.finish(Some(BodyError::Abandoned));
298        }
299    }
300}
301
302impl<D: AsRef<[u8]> + Send> WritableBody for BodyWriter<D> {
303    type Data = D;
304    type Error = BodyError;
305    async fn write_frame(
306        &mut self,
307        frame: BodyFrame<D>,
308        cancellation: &Cancellation,
309    ) -> Result<(), BodyError> {
310        BodyWriter::write_frame(self, frame, cancellation).await
311    }
312    async fn write(&mut self, data: D, cancellation: &Cancellation) -> Result<(), BodyError> {
313        BodyWriter::write(self, data, cancellation).await
314    }
315    async fn flush(&mut self, cancellation: &Cancellation) -> Result<(), BodyError> {
316        BodyWriter::flush(self, cancellation).await
317    }
318    fn close_write(&mut self, error: Option<BodyError>) {
319        BodyWriter::close_write(self, error)
320    }
321    fn abort(&mut self) {
322        BodyWriter::abort(self)
323    }
324}