Skip to main content

qubit_io/buffered/
async_buffered_output.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9use std::collections::TryReserveError;
10use std::future::poll_fn;
11use std::io;
12use std::io::Error;
13use std::io::ErrorKind;
14use std::pin::Pin;
15use std::task::Context;
16use std::task::Poll;
17
18use crate::AsyncClose;
19use crate::AsyncOutput;
20use crate::Buffer;
21use crate::async_io::MAX_READY_OPERATIONS_PER_POLL;
22use crate::buffered::DEFAULT_BUFFER_CAPACITY;
23use crate::traits::normalize_async_error;
24
25/// Buffered asynchronous item output.
26///
27/// Accepted items remain owned by this wrapper until the inner output accepts
28/// them. Partial writes are committed before another [`Poll::Pending`], making
29/// flushing cancellation-safe. Dropping this type cannot perform asynchronous
30/// I/O; callers that need delivery guarantees must poll `flush` to completion
31/// or recover the pending buffer through [`Self::into_parts`].
32///
33/// # Type Parameters
34///
35/// - `O`: Asynchronous item output type.
36#[must_use]
37#[derive(Debug)]
38pub struct AsyncBufferedOutput<O>
39where
40    O: AsyncOutput,
41    O::Item: Clone + Default,
42{
43    /// Asynchronous output receiving buffered items.
44    inner: O,
45    /// Storage retaining accepted but undelivered items.
46    buffer: Buffer<O::Item>,
47}
48
49impl<O> AsyncBufferedOutput<O>
50where
51    O: AsyncOutput,
52    O::Item: Clone + Default,
53{
54    /// Creates a buffered output with the default item capacity.
55    ///
56    /// # Parameters
57    ///
58    /// - `inner`: Asynchronous item output to buffer.
59    ///
60    /// # Returns
61    ///
62    /// Returns a buffered output with [`DEFAULT_BUFFER_CAPACITY`] items.
63    ///
64    /// # Panics
65    ///
66    /// Panics if `O::Item::default()` or `O::Item::clone()` panics, or the
67    /// default backing length exceeds [`Vec`]'s supported capacity.
68    #[inline(always)]
69    pub fn new(inner: O) -> Self {
70        Self::with_capacity(inner, DEFAULT_BUFFER_CAPACITY)
71    }
72
73    /// Creates a buffered output with a requested item capacity.
74    ///
75    /// # Parameters
76    ///
77    /// - `inner`: Asynchronous item output to buffer.
78    /// - `capacity`: Requested number of buffered items.
79    ///
80    /// # Returns
81    ///
82    /// Returns a buffered output whose actual capacity is at least one.
83    ///
84    /// # Panics
85    ///
86    /// Panics if `O::Item::default()` or `O::Item::clone()` panics, or the
87    /// requested backing length exceeds [`Vec`]'s supported capacity.
88    #[inline]
89    pub fn with_capacity(inner: O, capacity: usize) -> Self {
90        Self {
91            inner,
92            buffer: Buffer::with_capacity(capacity),
93        }
94    }
95
96    /// Tries to create a buffered output with a requested item capacity.
97    ///
98    /// # Parameters
99    ///
100    /// - `inner`: Asynchronous item output to buffer.
101    /// - `capacity`: Requested number of buffered items.
102    ///
103    /// # Returns
104    ///
105    /// Returns a buffered output whose actual capacity is at least one.
106    ///
107    /// # Errors
108    ///
109    /// Returns the allocation error when the backing buffer cannot be
110    /// allocated.
111    ///
112    /// # Panics
113    ///
114    /// Panics if initializing the backing buffer requires
115    /// `O::Item::default()` or `O::Item::clone()` and either operation panics.
116    #[inline]
117    pub fn try_with_capacity(inner: O, capacity: usize) -> Result<Self, TryReserveError> {
118        Ok(Self {
119            inner,
120            buffer: Buffer::try_with_capacity(capacity)?,
121        })
122    }
123
124    /// Returns a shared reference to the wrapped output.
125    ///
126    /// # Returns
127    ///
128    /// Returns the wrapped output. Items may still be pending in this wrapper.
129    #[inline(always)]
130    #[must_use]
131    pub const fn inner(&self) -> &O {
132        &self.inner
133    }
134
135    /// Returns a mutable reference to the wrapped output.
136    ///
137    /// Direct output calls can be ordered before items retained in the buffer.
138    ///
139    /// # Returns
140    ///
141    /// Returns the wrapped output.
142    #[inline(always)]
143    #[must_use]
144    pub fn inner_mut(&mut self) -> &mut O {
145        &mut self.inner
146    }
147
148    /// Consumes this wrapper without flushing the wrapped output.
149    ///
150    /// This method does not call [`AsyncOutput::flush_async`] and performs no
151    /// asynchronous I/O. Call [`AsyncOutput::flush_async`] before this method
152    /// for normal completion. Otherwise, the returned buffer contains the
153    /// pending items that the caller must write before continuing the logical
154    /// stream.
155    ///
156    /// # Returns
157    ///
158    /// Returns the wrapped output and pending item buffer.
159    #[inline(always)]
160    #[must_use = "the returned inner output and pending buffer must be handled"]
161    pub fn into_parts(self) -> (O, Buffer<O::Item>) {
162        (self.inner, self.buffer)
163    }
164
165    /// Returns the internal item capacity.
166    ///
167    /// # Returns
168    ///
169    /// Returns the total number of items in the backing buffer.
170    #[inline(always)]
171    #[must_use]
172    pub fn capacity(&self) -> usize {
173        self.buffer.capacity()
174    }
175
176    /// Returns the number of pending buffered items.
177    ///
178    /// # Returns
179    ///
180    /// Returns the readable-window length awaiting delivery.
181    #[inline(always)]
182    #[must_use]
183    pub const fn pending_len(&self) -> usize {
184        self.buffer.available()
185    }
186
187    /// Returns the pending buffered item window.
188    ///
189    /// # Returns
190    ///
191    /// Returns items accepted by this wrapper but not yet accepted by the
192    /// inner output.
193    #[inline(always)]
194    #[must_use]
195    pub fn pending(&self) -> &[O::Item] {
196        self.buffer.readable()
197    }
198
199    /// Tries to ensure that the internal item capacity is at least `capacity`.
200    ///
201    /// Pending items are retained and this method performs no I/O.
202    ///
203    /// # Parameters
204    ///
205    /// - `capacity`: Minimum total item capacity to reserve.
206    ///
207    /// # Returns
208    ///
209    /// Returns `Ok(())` after the backing buffer has at least `capacity`
210    /// item slots.
211    ///
212    /// # Errors
213    ///
214    /// Returns the allocation error when the backing buffer cannot grow.
215    ///
216    /// # Panics
217    ///
218    /// Panics if growing the backing buffer requires `O::Item::default()` or
219    /// `O::Item::clone()` and either operation panics.
220    #[inline(always)]
221    pub fn try_reserve_capacity(&mut self, capacity: usize) -> Result<(), TryReserveError> {
222        self.buffer.try_reserve_capacity(capacity)
223    }
224
225    /// Returns the unused capacity in the internal buffer.
226    ///
227    /// # Returns
228    ///
229    /// Returns the number of items that can be buffered without draining.
230    #[inline(always)]
231    #[must_use]
232    pub fn spare_capacity(&self) -> usize {
233        self.buffer.spare_capacity()
234    }
235
236    /// Returns the full backing storage and its spare-tail range.
237    ///
238    /// Call [`Self::advance`] after writing initialized items into the returned
239    /// spare range.
240    ///
241    /// # Returns
242    ///
243    /// Returns the backing storage together with the first and past-the-end
244    /// indexes of its spare range.
245    #[inline(always)]
246    #[must_use]
247    pub fn spare_raw_parts_mut(&mut self) -> (&mut [O::Item], usize, usize) {
248        self.buffer.spare_raw_parts_mut()
249    }
250
251    /// Advances the pending-item limit without checking bounds.
252    ///
253    /// # Parameters
254    ///
255    /// - `count`: Number of initialized spare items to mark as pending.
256    ///
257    /// # Safety
258    ///
259    /// The caller must guarantee that `count <= self.spare_capacity()` and
260    /// that the corresponding spare items have been initialized.
261    #[inline(always)]
262    pub unsafe fn advance(&mut self, count: usize) {
263        // SAFETY: The caller guarantees that initialized items fit the spare
264        // tail.
265        unsafe {
266            self.buffer.advance(count);
267        }
268    }
269
270    /// Polls delivery of pending items when `count` spare items are needed.
271    ///
272    /// # Parameters
273    ///
274    /// - `cx`: Task context used to register a wake-up.
275    /// - `count`: Minimum number of spare item slots to make available.
276    ///
277    /// # Returns
278    ///
279    /// Returns a ready success when the requested spare capacity is available,
280    /// or [`Poll::Pending`] while the wrapped output is not ready.
281    ///
282    /// # Errors
283    ///
284    /// Returns [`ErrorKind::InvalidInput`] when `count` exceeds the buffer
285    /// capacity, [`ErrorKind::WriteZero`] when draining makes no progress, or
286    /// an error reported by the wrapped output.
287    pub fn poll_ensure_spare_capacity(
288        mut self: Pin<&mut Self>,
289        cx: &mut Context<'_>,
290        count: usize,
291    ) -> Poll<io::Result<()>> {
292        if count > self.as_ref().get_ref().buffer.capacity() {
293            return Poll::Ready(Err(Error::new(
294                ErrorKind::InvalidInput,
295                "requested spare capacity exceeds buffered output capacity",
296            )));
297        }
298        if self.as_ref().get_ref().buffer.spare_capacity() < count {
299            return self.as_mut().poll_drain_buffer(cx);
300        }
301        Poll::Ready(Ok(()))
302    }
303
304    /// Polls pending-item delivery without flushing the inner output.
305    ///
306    /// # Parameters
307    ///
308    /// - `cx`: Task context used to register a wake-up.
309    ///
310    /// # Returns
311    ///
312    /// Returns [`Poll::Pending`] while the inner output is not ready, or a
313    /// ready success after all pending items are delivered.
314    ///
315    /// # Errors
316    ///
317    /// Returns [`io::ErrorKind::WriteZero`] if the inner output accepts no
318    /// pending item. Other errors are propagated from the inner output.
319    fn poll_drain_buffer(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
320        // SAFETY: `inner` is never moved after projecting from this pinned
321        // wrapper. `buffer` does not structurally pin any value.
322        let this = unsafe { self.as_mut().get_unchecked_mut() };
323        let mut ready_operations = 0;
324        while !this.buffer.is_empty() {
325            let result = {
326                let pending = this.buffer.readable();
327                // SAFETY: The pinned wrapper never moves `inner`.
328                let inner = unsafe { Pin::new_unchecked(&mut this.inner) };
329                inner.poll_write(cx, pending)
330            };
331            match result {
332                Poll::Ready(Ok(0)) => {
333                    return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
334                }
335                Poll::Ready(Ok(written)) => {
336                    // SAFETY: `poll_write` validated the returned count against
337                    // the pending slice length.
338                    unsafe {
339                        this.buffer.consume(written);
340                    }
341                    ready_operations += 1;
342                    if !this.buffer.is_empty() && ready_operations >= MAX_READY_OPERATIONS_PER_POLL {
343                        cx.waker().wake_by_ref();
344                        return Poll::Pending;
345                    }
346                }
347                Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
348                Poll::Pending => return Poll::Pending,
349            }
350        }
351        this.buffer.clear();
352        Poll::Ready(Ok(()))
353    }
354}
355
356impl<O> AsyncBufferedOutput<O>
357where
358    O: AsyncOutput + Unpin,
359    O::Item: Clone + Default + Unpin,
360{
361    /// Asynchronously ensures that the pending buffer has room for at least
362    /// `count` more items.
363    ///
364    /// Pending items are written to the wrapped output when necessary; this
365    /// does not flush the wrapped output itself.
366    ///
367    /// # Parameters
368    ///
369    /// - `count`: Minimum number of spare item slots to make available.
370    ///
371    /// # Returns
372    ///
373    /// Returns `Ok(())` when the requested spare capacity is available.
374    ///
375    /// # Errors
376    ///
377    /// Returns [`ErrorKind::InvalidInput`] when `count` exceeds the buffer
378    /// capacity, [`ErrorKind::WriteZero`] when the wrapped output makes no
379    /// progress, or an error from the wrapped output.
380    pub async fn ensure_spare_capacity_async(&mut self, count: usize) -> io::Result<()> {
381        poll_fn(|cx| Pin::new(&mut *self).poll_ensure_spare_capacity(cx, count)).await
382    }
383}
384
385impl<O> AsyncOutput for AsyncBufferedOutput<O>
386where
387    O: AsyncOutput,
388    O::Item: Clone + Default,
389{
390    /// Item type accepted by the wrapped output.
391    type Item = O::Item;
392
393    /// Reports that this output already buffers items.
394    ///
395    /// # Returns
396    ///
397    /// Always returns `true`.
398    #[inline(always)]
399    fn is_buffered(&self) -> bool {
400        true
401    }
402
403    /// Polls one write through the retained item buffer.
404    ///
405    /// A zero-length request completes immediately. The method first uses
406    /// spare buffer capacity, then drains pending items when necessary.
407    ///
408    /// # Parameters
409    ///
410    /// - `cx`: Task context used to register a wake-up.
411    /// - `input`: Source item slice.
412    /// - `index`: Starting source index.
413    /// - `count`: Maximum number of items to accept.
414    ///
415    /// # Returns
416    ///
417    /// Returns [`Poll::Pending`] when pending items cannot yet be delivered. A
418    /// ready success contains the number of newly accepted items.
419    ///
420    /// # Errors
421    ///
422    /// Returns [`io::ErrorKind::WriteZero`] if draining makes no progress.
423    /// Other errors are propagated from the wrapped output.
424    ///
425    /// # Panics
426    ///
427    /// May panic if a nonzero requested input range does not fit. Debug builds
428    /// validate buffered-copy ranges before copying.
429    ///
430    /// # Safety
431    ///
432    /// The range `index..index + count` must be valid for `input`.
433    unsafe fn poll_write_unchecked(
434        mut self: Pin<&mut Self>,
435        cx: &mut Context<'_>,
436        input: &[Self::Item],
437        index: usize,
438        count: usize,
439    ) -> Poll<io::Result<usize>> {
440        if count == 0 {
441            return Poll::Ready(Ok(0));
442        }
443
444        // SAFETY: This projection only inspects the unpinned buffer field.
445        let (spare, capacity) = unsafe {
446            let this = self.as_mut().get_unchecked_mut();
447            (this.buffer.spare_capacity(), this.buffer.capacity())
448        };
449        // Keep exact remaining-space writes buffered, but let a full-capacity
450        // write into an empty buffer take the direct path below.
451        if count <= spare && count < capacity {
452            // SAFETY: The caller guarantees the source range and the branch
453            // proves that the destination spare range is large enough.
454            unsafe {
455                self.as_mut().get_unchecked_mut().buffer.copy_from(input, index, count);
456            }
457            return Poll::Ready(Ok(count));
458        }
459
460        match self.as_mut().poll_drain_buffer(cx) {
461            Poll::Ready(Ok(())) => {}
462            Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
463            Poll::Pending => return Poll::Pending,
464        }
465
466        // SAFETY: `inner` is never moved after projecting from this pinned
467        // wrapper.
468        let this = unsafe { self.as_mut().get_unchecked_mut() };
469        if count >= capacity {
470            let source = &input[index..index + count];
471            // SAFETY: The pinned wrapper never moves `inner`.
472            let inner = unsafe { Pin::new_unchecked(&mut this.inner) };
473            return inner.poll_write(cx, source);
474        }
475
476        // SAFETY: Draining cleared the buffer, `count` fits its total
477        // capacity, and the caller guarantees the source range.
478        unsafe {
479            this.buffer.copy_from(input, index, count);
480        }
481        Poll::Ready(Ok(count))
482    }
483
484    /// Polls delivery of pending items followed by the inner flush operation.
485    ///
486    /// # Parameters
487    ///
488    /// - `cx`: Task context used to register a wake-up.
489    ///
490    /// # Returns
491    ///
492    /// Returns [`Poll::Pending`] while delivery or flushing is incomplete,
493    /// otherwise a ready success result.
494    ///
495    /// # Errors
496    ///
497    /// Returns [`io::ErrorKind::WriteZero`] if draining makes no progress, or
498    /// an error reported by the wrapped output. Invalid asynchronous error
499    /// kinds from the flush operation are normalized to
500    /// [`io::ErrorKind::InvalidData`].
501    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
502        match self.as_mut().poll_drain_buffer(cx) {
503            Poll::Ready(Ok(())) => {}
504            Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
505            Poll::Pending => return Poll::Pending,
506        }
507        // SAFETY: The pinned wrapper never moves `inner`.
508        let this = unsafe { self.get_unchecked_mut() };
509        // SAFETY: `inner` remains pinned in place for this call.
510        unsafe { Pin::new_unchecked(&mut this.inner) }
511            .poll_flush(cx)
512            .map(|result| result.map_err(normalize_async_error))
513    }
514}
515
516impl<O> AsyncClose for AsyncBufferedOutput<O>
517where
518    O: AsyncClose,
519    O::Item: Clone + Default,
520{
521    /// Polls delivery of pending items followed by closing the inner output.
522    ///
523    /// # Parameters
524    ///
525    /// - `cx`: Task context used to register a wake-up.
526    ///
527    /// # Returns
528    ///
529    /// Returns [`Poll::Pending`] while delivery or closing is incomplete,
530    /// otherwise a ready success result.
531    ///
532    /// # Errors
533    ///
534    /// Returns [`io::ErrorKind::WriteZero`] if draining makes no progress, or
535    /// an error reported by the wrapped output. Invalid asynchronous error
536    /// kinds from the close operation are normalized to
537    /// [`io::ErrorKind::InvalidData`].
538    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
539        match self.as_mut().poll_drain_buffer(cx) {
540            Poll::Ready(Ok(())) => {}
541            Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
542            Poll::Pending => return Poll::Pending,
543        }
544        // SAFETY: The pinned wrapper never moves `inner`.
545        let this = unsafe { self.get_unchecked_mut() };
546        // SAFETY: `inner` remains pinned in place for this call.
547        unsafe { Pin::new_unchecked(&mut this.inner) }
548            .poll_close(cx)
549            .map(|result| result.map_err(normalize_async_error))
550    }
551}