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}