Skip to main content

tokio_quiche/quic/io/
worker.rs

1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use std::net::SocketAddr;
28use std::ops::ControlFlow;
29use std::sync::Arc;
30use std::task::Poll;
31use std::time::Duration;
32use std::time::Instant;
33#[cfg(feature = "perf-quic-listener-metrics")]
34use std::time::SystemTime;
35
36use super::connection_stage::Close;
37use super::connection_stage::ConnectionStage;
38use super::connection_stage::ConnectionStageContext;
39use super::connection_stage::Handshake;
40use super::connection_stage::RunningApplication;
41use super::gso::*;
42use super::utilization_estimator::BandwidthReporter;
43
44use crate::metrics::labels;
45use crate::metrics::Metrics;
46use crate::quic::connection::ApplicationOverQuic;
47use crate::quic::connection::HandshakeError;
48use crate::quic::connection::Incoming;
49use crate::quic::connection::QuicConnectionStats;
50use crate::quic::connection::SharedConnectionIdGenerator;
51use crate::quic::router::ConnectionMapCommand;
52use crate::quic::QuicheConnection;
53use crate::QuicResult;
54
55use boring::ssl::SslRef;
56use datagram_socket::DatagramSocketSend;
57use datagram_socket::DatagramSocketSendExt;
58use datagram_socket::MaybeConnectedSocket;
59use datagram_socket::QuicAuditStats;
60use foundations::telemetry::log;
61use quiche::ConnectionId;
62use quiche::Error as QuicheError;
63use quiche::SendInfo;
64use tokio::select;
65use tokio::sync::mpsc;
66use tokio::time;
67
68// Number of incoming packets to be buffered in the incoming channel.
69pub(crate) const INCOMING_QUEUE_SIZE: usize = 2048;
70
71// Check if there are any incoming packets while sending data every this number
72// of sent packets
73pub(crate) const CHECK_INCOMING_QUEUE_RATIO: usize = INCOMING_QUEUE_SIZE / 16;
74
75const RELEASE_TIMER_THRESHOLD: Duration = Duration::from_micros(250);
76
77/// Stop queuing GSO packets, if packet size is below this threshold.
78const GSO_THRESHOLD: usize = 1_000;
79
80/// Size of each full egress buffer borrowed for a send burst.
81///
82/// Matches the maximum quiche buffer size so GSO batching is unaffected while
83/// a connection is actively sending. Unlike a persistent per-connection
84/// buffer, this memory is returned to the per-worker [`SEND_BUF_POOL`] before
85/// the worker sleeps. The free list retains it for reuse (rather than truly
86/// freeing it) but no idle connection owns an egress buffer.
87const SEND_BUFFER_SIZE: usize = crate::buf_factory::BufFactory::MAX_BUF_SIZE;
88
89/// Size of a temporary egress buffer when pooling is disabled.
90///
91/// The cold handshake and connection-close paths generate a single datagram at
92/// a time. When pooling is enabled, they borrow a full-size buffer from
93/// [`SEND_BUF_POOL`]; otherwise a one-MTU buffer is enough.
94const TRANSIENT_SEND_BUFFER_SIZE: usize = 1500;
95
96/// Allocates a zero-initialized egress buffer on the heap.
97///
98/// This is the cold path that fills [`SEND_BUF_POOL`] on a miss; steady-state
99/// bursts borrow a recycled buffer via [`PooledSendBuf::acquire`] and never
100/// hit this. The buffer is boxed (never a stack array) so that holding it
101/// across the `.await` in [`IoWorker::flush_buffer_to_socket`] keeps the
102/// worker's futures small and avoids uncontrolled stack growth.
103fn alloc_send_buffer() -> Box<[u8]> {
104    vec![0u8; SEND_BUFFER_SIZE].into_boxed_slice()
105}
106
107thread_local! {
108    /// Per-runtime-worker free-list of egress scratch buffers.
109    ///
110    /// A buffer is borrowed for a single send burst and returned on drop, so
111    /// its pages stay resident across bursts (no per-burst page fault or
112    /// kernel zero-fill) while no idle connection retains a buffer: the pool
113    /// holds at most [`SEND_BUF_POOL_CAP`] buffers per worker thread,
114    /// independent of the connection count.
115    static SEND_BUF_POOL: std::cell::RefCell<Vec<Box<[u8]>>> =
116        const { std::cell::RefCell::new(Vec::new()) };
117}
118
119/// Upper bound on egress buffers parked per worker thread. The natural
120/// high-water mark is the number of connection tasks simultaneously suspended
121/// at a flush `.await` on one runtime thread; returns beyond the cap are freed
122/// so a burst spike cannot pin unbounded memory to a thread.
123///
124/// This is a fixed per-worker-thread reservation, independent of the
125/// connection count: up to `SEND_BUF_POOL_CAP * SEND_BUFFER_SIZE`
126/// (16 * 64 KiB = 1 MiB) per runtime worker thread. The pool is not shrunk
127/// once grown, so after a burst it stays at its high-water mark for the
128/// process lifetime.
129const SEND_BUF_POOL_CAP: usize = 16;
130
131/// Egress scratch buffer borrowed from the per-thread [`SEND_BUF_POOL`] and
132/// returned to it on drop.
133///
134/// Behaves like the `Box<[u8]>` it replaces via `Deref`/`DerefMut`, so idle
135/// connections still retain no egress buffer, but the backing pages are
136/// recycled instead of re-faulted (and re-zeroed by the kernel) every burst.
137struct PooledSendBuf(Box<[u8]>);
138
139impl PooledSendBuf {
140    fn acquire() -> Self {
141        let buf = SEND_BUF_POOL
142            .with(|pool| pool.borrow_mut().pop())
143            .unwrap_or_else(|| {
144                // Pool miss (cold path): allocate a fresh buffer and record it.
145                // Re-using a parked buffer (the hot path) does not touch any
146                // counter.
147                crate::metrics::quic::send_buffer_pool_allocated().inc();
148                alloc_send_buffer()
149            });
150        // No re-zeroing on reuse: quiche writes only the bytes it emits and
151        // the flush path transmits solely `send_buf[..bytes_written]`, so any
152        // stale bytes left by a previous burst are never sent.
153        Self(buf)
154    }
155}
156
157impl Drop for PooledSendBuf {
158    fn drop(&mut self) {
159        // Move the buffer out, leaving an empty (non-allocating) boxed slice
160        // behind, so it can be returned to the pool. Storing a plain
161        // `Box<[u8]>` rather than an `Option` keeps `Deref`/`DerefMut`
162        // panic-free.
163        let buf = std::mem::take(&mut self.0);
164        // Returns to *this* thread's pool. With tokio work-stealing a task may
165        // migrate across the flush `.await`, so a buffer can be acquired on one
166        // worker and returned on another; this is benign, and the per-thread
167        // cap keeps the total bounded by `cap * workers`.
168        SEND_BUF_POOL.with(|pool| {
169            let mut pool = pool.borrow_mut();
170            if pool.len() < SEND_BUF_POOL_CAP {
171                pool.push(buf);
172            } else {
173                // Pool already at capacity (cold path, only under burst
174                // spikes): drop the buffer instead of parking it, and record
175                // the discard. Returning to a non-full pool (the hot path)
176                // does not touch any counter.
177                crate::metrics::quic::send_buffer_pool_discarded().inc();
178            }
179        });
180    }
181}
182
183impl std::ops::Deref for PooledSendBuf {
184    type Target = [u8];
185
186    fn deref(&self) -> &[u8] {
187        &self.0
188    }
189}
190
191impl std::ops::DerefMut for PooledSendBuf {
192    fn deref_mut(&mut self) -> &mut [u8] {
193        &mut self.0
194    }
195}
196
197/// Egress buffer used briefly outside the main write loop.
198///
199/// This preserves the runtime pooling switch for handshake and close paths:
200/// pooling borrows a full-size recycled buffer, while disabling it allocates a
201/// small one-off buffer.
202enum TransientSendBuf {
203    Pooled(PooledSendBuf),
204    Unpooled(Box<[u8]>),
205}
206
207impl TransientSendBuf {
208    fn acquire(pool_send_buffer: bool) -> Self {
209        if pool_send_buffer {
210            Self::Pooled(PooledSendBuf::acquire())
211        } else {
212            Self::Unpooled(
213                vec![0u8; TRANSIENT_SEND_BUFFER_SIZE].into_boxed_slice(),
214            )
215        }
216    }
217}
218
219impl AsRef<[u8]> for TransientSendBuf {
220    fn as_ref(&self) -> &[u8] {
221        match self {
222            Self::Pooled(buf) => &buf[..],
223            Self::Unpooled(buf) => &buf[..],
224        }
225    }
226}
227
228impl AsMut<[u8]> for TransientSendBuf {
229    fn as_mut(&mut self) -> &mut [u8] {
230        match self {
231            Self::Pooled(buf) => &mut buf[..],
232            Self::Unpooled(buf) => &mut buf[..],
233        }
234    }
235}
236
237pub struct WriterConfig {
238    pub pending_cid: Option<ConnectionId<'static>>,
239    pub peer_addr: SocketAddr,
240    pub local_addr: SocketAddr,
241    pub with_gso: bool,
242    pub pacing_offload: bool,
243    pub with_pktinfo: bool,
244    /// Whether the worker borrows its egress buffer from a per-worker-thread
245    /// pool for each send burst. When `false`, the worker keeps a persistent
246    /// per-connection buffer for its lifetime instead.
247    pub pool_send_buffer: bool,
248}
249
250#[derive(Default)]
251pub(crate) struct WriteState {
252    conn_established: bool,
253    bytes_written: usize,
254    segment_size: usize,
255    num_pkts: usize,
256    tx_time: Option<Instant>,
257    has_pending_data: bool,
258    // If pacer schedules packets too far into the future, we want to pause
259    // sending, until the future arrives
260    next_release_time: Option<Instant>,
261    // The selected source and destination addresses for the current write
262    // cycle.
263    selected_path: Option<(SocketAddr, SocketAddr)>,
264    // Iterator over the network paths that haven't been flushed yet.
265    pending_paths: quiche::SocketAddrIter,
266}
267
268pub(crate) struct IoWorkerParams<Tx, M> {
269    pub(crate) socket: MaybeConnectedSocket<Tx>,
270    pub(crate) shutdown_tx: mpsc::Sender<()>,
271    pub(crate) cfg: WriterConfig,
272    pub(crate) audit_log_stats: Arc<QuicAuditStats>,
273    pub(crate) write_state: WriteState,
274    pub(crate) conn_map_cmd_tx: mpsc::UnboundedSender<ConnectionMapCommand>,
275    pub(crate) cid_generator: Option<SharedConnectionIdGenerator>,
276    #[cfg(feature = "perf-quic-listener-metrics")]
277    pub(crate) init_rx_time: Option<SystemTime>,
278    pub(crate) metrics: M,
279}
280
281pub(crate) struct IoWorker<Tx, M, S> {
282    socket: MaybeConnectedSocket<Tx>,
283    /// A field that signals to the listener task that the connection has gone
284    /// away (nothing is sent here, listener task just detects the sender
285    /// has dropped)
286    shutdown_tx: mpsc::Sender<()>,
287    cfg: WriterConfig,
288    audit_log_stats: Arc<QuicAuditStats>,
289    write_state: WriteState,
290    conn_map_cmd_tx: mpsc::UnboundedSender<ConnectionMapCommand>,
291    cid_generator: Option<SharedConnectionIdGenerator>,
292    #[cfg(feature = "perf-quic-listener-metrics")]
293    init_rx_time: Option<SystemTime>,
294    metrics: M,
295    conn_stage: S,
296    bw_estimator: BandwidthReporter,
297}
298
299impl<Tx, M, S> IoWorker<Tx, M, S>
300where
301    Tx: DatagramSocketSend + Send,
302    M: Metrics,
303    S: ConnectionStage,
304{
305    pub(crate) fn new(params: IoWorkerParams<Tx, M>, conn_stage: S) -> Self {
306        let bw_estimator =
307            BandwidthReporter::new(params.metrics.utilized_bandwidth());
308
309        log::trace!("Creating IoWorker with stage: {conn_stage:?}");
310
311        Self {
312            socket: params.socket,
313            shutdown_tx: params.shutdown_tx,
314            cfg: params.cfg,
315            audit_log_stats: params.audit_log_stats,
316            write_state: params.write_state,
317            conn_map_cmd_tx: params.conn_map_cmd_tx,
318            cid_generator: params.cid_generator,
319            #[cfg(feature = "perf-quic-listener-metrics")]
320            init_rx_time: params.init_rx_time,
321            metrics: params.metrics,
322            conn_stage,
323            bw_estimator,
324        }
325    }
326
327    fn fill_available_scids(&self, qconn: &mut QuicheConnection) {
328        if qconn.scids_left() == 0 {
329            return;
330        }
331        let Some(cid_generator) = self.cid_generator.as_deref() else {
332            return;
333        };
334
335        let current_cid = qconn.source_id().into_owned();
336        for _ in 0..qconn.scids_left() {
337            // We don't emit stateless resets, so any unguessable value is fine
338            let reset_token = random_u128();
339            let new_cid = cid_generator.new_connection_id();
340
341            if self
342                .conn_map_cmd_tx
343                .send(ConnectionMapCommand::MapCid {
344                    existing_cid: current_cid.clone(),
345                    new_cid: new_cid.clone(),
346                })
347                .is_err()
348            {
349                // Can't do anything if the connection map is gone
350                return;
351            }
352
353            if qconn.new_scid(&new_cid, reset_token, false).is_err() {
354                // This only fails if we have reached the CID limit already
355                return;
356            }
357        }
358    }
359
360    fn unmap_cid(&self, cid: ConnectionId<'static>) {
361        // If the connection map is gone, the ID is already "unmapped"
362        let _ = self
363            .conn_map_cmd_tx
364            .send(ConnectionMapCommand::UnmapCid(cid));
365    }
366
367    fn refresh_connection_ids(&self, qconn: &mut QuicheConnection) {
368        // Top up the connection's active CIDs
369        self.fill_available_scids(qconn);
370
371        // Remove retired CIDs from the ingress router
372        while let Some(retired_cid) = qconn.retired_scid_next() {
373            self.unmap_cid(retired_cid);
374        }
375    }
376
377    async fn work_loop<A: ApplicationOverQuic>(
378        &mut self, qconn: &mut QuicheConnection,
379        ctx: &mut ConnectionStageContext<A>,
380    ) -> QuicResult<()> {
381        const DEFAULT_SLEEP: Duration = Duration::from_secs(60);
382        let mut current_deadline: Option<Instant> = None;
383        let sleep = time::sleep(DEFAULT_SLEEP);
384        tokio::pin!(sleep);
385
386        // When pooling is disabled we keep one persistent egress buffer for the
387        // whole connection, owned here in the IO worker and held across the loop
388        // below (including while the connection is idle) -- the pre-pooling
389        // behavior. When pooling is enabled this stays `None` and each send
390        // burst borrows a transient buffer from the per-worker pool instead.
391        let mut persistent_send_buf: Option<Box<[u8]>> =
392            (!self.cfg.pool_send_buffer).then(alloc_send_buffer);
393
394        loop {
395            let now = Instant::now();
396
397            self.write_state.has_pending_data = true;
398
399            // Transient egress buffer for this wakeup's send burst when pooling
400            // is enabled. Borrowed from the per-worker pool on demand (see
401            // below) and returned after the burst, before the worker sleeps in
402            // the `select!` further down, so idle connections still hold no
403            // egress buffer. Stays `None` when a persistent buffer is used.
404            let mut pooled_send_buf: Option<PooledSendBuf> = None;
405
406            while self.write_state.has_pending_data {
407                let mut packets_sent = 0;
408
409                // Try to clear all received packets every so often, because
410                // incoming packets contain acks, and because the
411                // receive queue has a very limited size, once it is full incoming
412                // packets get stalled indefinitely
413                let mut did_recv = false;
414                while let Some(pkt) = ctx
415                    .in_pkt
416                    .take()
417                    .or_else(|| ctx.incoming_pkt_receiver.try_recv().ok())
418                {
419                    self.process_incoming(qconn, pkt)?;
420                    did_recv = true;
421                }
422
423                self.conn_stage.on_read(did_recv, qconn, ctx)?;
424                self.refresh_connection_ids(qconn);
425
426                let can_release = match self.write_state.next_release_time {
427                    None => true,
428                    Some(next_release) =>
429                        next_release
430                            .checked_duration_since(now)
431                            .unwrap_or_default() <
432                            RELEASE_TIMER_THRESHOLD,
433                };
434
435                self.write_state.has_pending_data &= can_release;
436
437                while self.write_state.has_pending_data &&
438                    packets_sent < CHECK_INCOMING_QUEUE_RATIO
439                {
440                    // Use the persistent per-connection buffer when pooling is
441                    // disabled. Otherwise borrow a buffer from the per-worker
442                    // pool on the first gather of this send burst and reuse it
443                    // for the remainder of the burst; it is returned at the end
444                    // of the enclosing block (below), before the worker sleeps,
445                    // so idle connections hold no egress buffer.
446                    let send_buf: &mut [u8] =
447                        if let Some(buf) = persistent_send_buf.as_deref_mut() {
448                            buf
449                        } else {
450                            &mut pooled_send_buf
451                                .get_or_insert_with(PooledSendBuf::acquire)[..]
452                        };
453
454                    self.gather_data_from_quiche_conn(qconn, send_buf, false)?;
455
456                    // Break if the connection is closed
457                    if qconn.is_closed() {
458                        return Ok(());
459                    }
460
461                    let mut flush_operation_token =
462                        TrackMidHandshakeFlush::new(self.metrics.clone());
463
464                    self.flush_buffer_to_socket(&send_buf[..]).await;
465
466                    flush_operation_token.mark_complete();
467
468                    packets_sent += self.write_state.num_pkts;
469
470                    if let ControlFlow::Break(reason) =
471                        self.conn_stage.on_flush(qconn, ctx)
472                    {
473                        return reason;
474                    }
475                }
476            }
477
478            // Return the borrowed egress buffer to the per-worker pool before
479            // sleeping so it is not held while the connection is idle. The
480            // persistent buffer (when pooling is disabled) is intentionally
481            // kept across sleeps for the connection's lifetime.
482            drop(pooled_send_buf);
483
484            self.bw_estimator.update(qconn, now);
485
486            self.audit_log_stats
487                .set_max_bandwidth(self.bw_estimator.max_bandwidth);
488            self.audit_log_stats.set_max_loss_pct(
489                (self.bw_estimator.max_loss_pct * 100_f32).round() as u8,
490            );
491
492            let new_deadline = min_of_some(
493                qconn.timeout_instant(),
494                self.write_state.next_release_time,
495            );
496            let new_deadline =
497                min_of_some(new_deadline, self.conn_stage.wait_deadline());
498
499            if new_deadline != current_deadline {
500                current_deadline = new_deadline;
501
502                sleep
503                    .as_mut()
504                    .reset(new_deadline.unwrap_or(now + DEFAULT_SLEEP).into());
505            }
506
507            let incoming_recv = &mut ctx.incoming_pkt_receiver;
508            let application = &mut ctx.application;
509
510            select! {
511                biased;
512                () = &mut sleep => {
513                    // It's very important that we keep the timeout arm at the top of this loop so
514                    // that we poll it every time we need to. Since this is a biased `select!`, if
515                    // we put this behind another arm, we could theoretically starve the sleep arm
516                    // and hang connections.
517                    //
518                    // See https://docs.rs/tokio/latest/tokio/macro.select.html#fairness for more
519                    qconn.on_timeout();
520
521                    self.write_state.next_release_time = None;
522                    current_deadline = None;
523                    sleep.as_mut().reset((now + DEFAULT_SLEEP).into());
524                }
525                Some(pkt) = incoming_recv.recv() => ctx.in_pkt = Some(pkt),
526                directive = self.wait_for_data_or_handshake(qconn, application) => {
527                    match directive? {
528                        WaitForDataOrHandshakeDirective::Flush(send_buf) => {
529                            // The handshake data was gathered into this
530                            // on-demand buffer; flush it here (outside the
531                            // select! so the flush cannot be cancelled), then
532                            // return it to the pool or drop it.
533                            self.flush_buffer_to_socket(send_buf.as_ref()).await;
534                        }
535                        WaitForDataOrHandshakeDirective::Noop => {}
536                    }
537                },
538            };
539
540            if let ControlFlow::Break(reason) = self.conn_stage.post_wait(qconn) {
541                return reason;
542            }
543        }
544    }
545
546    #[cfg(feature = "perf-quic-listener-metrics")]
547    fn measure_complete_handshake_time(&mut self) {
548        if let Some(init_rx_time) = self.init_rx_time.take() {
549            if let Ok(delta) = init_rx_time.elapsed() {
550                self.metrics
551                    .handshake_time_seconds(
552                        labels::QuicHandshakeStage::HandshakeResponse,
553                    )
554                    .observe(delta.as_nanos() as u64);
555            }
556        }
557    }
558
559    /// Gathers one or more packets from quiche into `send_buf`.
560    ///
561    /// A single-packet gather leaves a full buffer available for the next
562    /// packet instead of generating a short packet in the remaining tail.
563    fn gather_data_from_quiche_conn(
564        &mut self, qconn: &mut QuicheConnection, send_buf: &mut [u8],
565        single_packet: bool,
566    ) -> QuicResult<usize> {
567        let mut segment_size = None;
568        let mut send_info = None;
569
570        self.write_state.num_pkts = 0;
571        self.write_state.bytes_written = 0;
572
573        self.write_state.selected_path = None;
574
575        let now = Instant::now();
576
577        let send_buf = {
578            let trunc = UDP_MAX_GSO_PACKET_SIZE.min(send_buf.len());
579            &mut send_buf[..trunc]
580        };
581
582        #[cfg(feature = "gcongestion")]
583        let gcongestion_enabled = true;
584
585        #[cfg(not(feature = "gcongestion"))]
586        let gcongestion_enabled = qconn.gcongestion_enabled().unwrap_or(false);
587
588        let initial_release_decision = if gcongestion_enabled {
589            let initial_release_decision = qconn
590                .get_next_release_time()
591                .filter(|_| self.pacing_enabled(qconn));
592
593            if let Some(future_release_time) =
594                initial_release_decision.as_ref().and_then(|v| v.time(now))
595            {
596                let max_into_fut = qconn.max_release_into_future();
597
598                if future_release_time.duration_since(now) >= max_into_fut {
599                    self.write_state.next_release_time =
600                        Some(now + max_into_fut.mul_f32(0.8));
601                    self.write_state.has_pending_data = false;
602                    return Ok(0);
603                }
604            }
605
606            initial_release_decision
607        } else {
608            None
609        };
610
611        let buffer_write_outcome = loop {
612            let outcome = self.write_packet_to_buffer(
613                qconn,
614                send_buf,
615                &mut send_info,
616                segment_size,
617            );
618
619            let packet_size = match outcome {
620                Ok(0) => break Ok(0),
621
622                Ok(bytes_written) => bytes_written,
623
624                Err(e) => break Err(e),
625            };
626
627            // Flush after one packet when GSO is disabled or the caller needs
628            // each packet to start with a full buffer.
629            if single_packet || !self.cfg.with_gso {
630                break outcome;
631            }
632
633            #[cfg(not(feature = "gcongestion"))]
634            let max_send_size = if !gcongestion_enabled {
635                // Only call qconn.send_quantum when !gcongestion_enabled.
636                tune_max_send_size(
637                    segment_size,
638                    qconn.send_quantum(),
639                    send_buf.len(),
640                )
641            } else {
642                usize::MAX
643            };
644
645            #[cfg(feature = "gcongestion")]
646            let max_send_size = usize::MAX;
647
648            // If segment_size is known, update the maximum of
649            // GSO sender buffer size to the multiple of
650            // segment_size.
651            let buffer_is_full = self.write_state.num_pkts ==
652                UDP_MAX_SEGMENT_COUNT ||
653                self.write_state.bytes_written >= max_send_size;
654
655            if buffer_is_full {
656                break outcome;
657            }
658
659            // Flush to network when the newly generated packet size is
660            // different from previously written packet, as GSO needs packets
661            // to have the same size, except for the last one in the buffer.
662            // The last packet may be smaller than the previous size.
663            match segment_size {
664                Some(size)
665                    if packet_size != size || packet_size < GSO_THRESHOLD =>
666                    break outcome,
667                None => segment_size = Some(packet_size),
668                _ => (),
669            }
670
671            if gcongestion_enabled {
672                // If the release time of next packet is different, or it can't be
673                // part of a burst, start the next batch
674                if let Some(initial_release_decision) = initial_release_decision {
675                    match qconn.get_next_release_time() {
676                        Some(release)
677                            if release.can_burst() ||
678                                release.time_eq(
679                                    &initial_release_decision,
680                                    now,
681                                ) => {},
682                        _ => break outcome,
683                    }
684                }
685            }
686        };
687
688        let tx_time = if gcongestion_enabled {
689            initial_release_decision
690                .filter(|_| self.pacing_enabled(qconn))
691                // Return the time from the release decision if release_decision.time > now, else None.
692                .and_then(|v| v.time(now))
693        } else {
694            send_info
695                .filter(|_| self.pacing_enabled(qconn))
696                .map(|v| v.at)
697        };
698
699        self.write_state.conn_established = qconn.is_established();
700        self.write_state.tx_time = tx_time;
701        self.write_state.segment_size =
702            segment_size.unwrap_or(self.write_state.bytes_written);
703
704        if !gcongestion_enabled {
705            if let Some(time) = tx_time {
706                const DEFAULT_MAX_INTO_FUTURE: Duration =
707                    Duration::from_millis(1);
708                if time
709                    .checked_duration_since(now)
710                    .map(|d| d > DEFAULT_MAX_INTO_FUTURE)
711                    .unwrap_or(false)
712                {
713                    self.write_state.next_release_time =
714                        Some(now + DEFAULT_MAX_INTO_FUTURE.mul_f32(0.8));
715                    self.write_state.has_pending_data = false;
716                    return Ok(0);
717                }
718            }
719        }
720
721        buffer_write_outcome
722    }
723
724    /// Selects a network path, if none already selected.
725    ///
726    /// This will return the first path available in the write state's
727    /// `pending_paths` iterator. If that is empty a new iterator will be
728    /// created by querying quiche itself.
729    ///
730    /// Note that the connection's statically configured local address will be
731    /// used to query quiche for available paths, so this can't handle multiple
732    /// local addresses currently.
733    fn select_path(
734        &mut self, qconn: &QuicheConnection,
735    ) -> Option<(SocketAddr, SocketAddr)> {
736        if self.write_state.selected_path.is_some() {
737            return self.write_state.selected_path;
738        }
739
740        let from = self.cfg.local_addr;
741
742        // Initialize paths iterator.
743        if self.write_state.pending_paths.len() == 0 {
744            self.write_state.pending_paths = qconn.paths_iter(from);
745        }
746
747        let to = self.write_state.pending_paths.next()?;
748
749        Some((from, to))
750    }
751
752    #[cfg(not(feature = "gcongestion"))]
753    fn pacing_enabled(&self, qconn: &QuicheConnection) -> bool {
754        self.cfg.pacing_offload && qconn.pacing_enabled()
755    }
756
757    #[cfg(feature = "gcongestion")]
758    fn pacing_enabled(&self, _qconn: &QuicheConnection) -> bool {
759        self.cfg.pacing_offload
760    }
761
762    fn write_packet_to_buffer(
763        &mut self, qconn: &mut QuicheConnection, send_buf: &mut [u8],
764        send_info: &mut Option<SendInfo>, segment_size: Option<usize>,
765    ) -> QuicResult<usize> {
766        let mut send_buf = &mut send_buf[self.write_state.bytes_written..];
767        if send_buf.len() > segment_size.unwrap_or(usize::MAX) {
768            // Never let the buffer be longer than segment size, for GSO to
769            // function properly.
770            send_buf = &mut send_buf[..segment_size.unwrap_or(usize::MAX)];
771        }
772
773        // On the first call to `select_path()` a path will be chosen based on
774        // the local address the connection initially landed on. Once a path is
775        // selected following calls to `select_path()` will return it, until it
776        // is reset at the start of the next write cycle.
777        //
778        // The path is then passed to `send_on_path()` which will only generate
779        // packets meant for that path, this way a single GSO buffer will only
780        // contain packets that belong to the same network path, which is
781        // required because the from/to addresses for each `sendmsg()` call
782        // apply to the whole GSO buffer.
783        let (from, to) = self.select_path(qconn).unzip();
784
785        match qconn.send_on_path(send_buf, from, to) {
786            Ok((packet_size, info)) => {
787                let _ = send_info.get_or_insert(info);
788
789                self.write_state.bytes_written += packet_size;
790                self.write_state.num_pkts += 1;
791
792                let from = send_info.as_ref().map(|info| info.from);
793                let to = send_info.as_ref().map(|info| info.to);
794
795                self.write_state.selected_path = from.zip(to);
796
797                self.write_state.has_pending_data = true;
798
799                Ok(packet_size)
800            },
801
802            Err(QuicheError::Done) => {
803                // Flush the current buffer to network. If no other path needs
804                // to be flushed to the network also yield the work loop task.
805                //
806                // Otherwise the write loop will start again and the next path
807                // will be selected.
808                let has_pending_paths = self.write_state.pending_paths.len() > 0;
809
810                // Keep writing if there are paths left to try.
811                self.write_state.has_pending_data = has_pending_paths;
812
813                Ok(0)
814            },
815
816            Err(e) => {
817                let error_code = if let Some(local_error) = qconn.local_error() {
818                    local_error.error_code
819                } else {
820                    let internal_error_code =
821                        quiche::WireErrorCode::InternalError as u64;
822                    let _ = qconn.close(false, internal_error_code, &[]);
823
824                    internal_error_code
825                };
826
827                self.audit_log_stats
828                    .set_sent_conn_close_transport_error_code(error_code as i64);
829
830                Err(Box::new(e))
831            },
832        }
833    }
834
835    async fn flush_buffer_to_socket(&mut self, send_buf: &[u8]) {
836        if self.write_state.bytes_written > 0 {
837            let current_send_buf = &send_buf[..self.write_state.bytes_written];
838
839            let (from, to) = self.write_state.selected_path.unzip();
840
841            let to = to.unwrap_or(self.cfg.peer_addr);
842            let from = from.filter(|_| self.cfg.with_pktinfo);
843
844            let send_res = if let (Some(udp_socket), true) =
845                (self.socket.as_udp_socket(), self.cfg.with_gso)
846            {
847                // Only UDP supports GSO.
848                send_to(
849                    udp_socket,
850                    to,
851                    from,
852                    current_send_buf,
853                    self.write_state.segment_size,
854                    self.write_state.tx_time,
855                    self.metrics
856                        .write_errors(labels::QuicWriteError::WouldBlock),
857                    self.metrics.send_to_wouldblock_duration_s(),
858                )
859                .await
860            } else {
861                self.socket.send_to(current_send_buf, to).await
862            };
863
864            #[cfg(feature = "perf-quic-listener-metrics")]
865            self.measure_complete_handshake_time();
866
867            match send_res {
868                Ok(n) =>
869                    if n < self.write_state.bytes_written {
870                        self.metrics
871                            .write_errors(labels::QuicWriteError::Partial)
872                            .inc();
873                    },
874
875                Err(_) => {
876                    self.metrics.write_errors(labels::QuicWriteError::Err).inc();
877                },
878            }
879        }
880    }
881
882    /// Process the incoming packet
883    fn process_incoming(
884        &mut self, qconn: &mut QuicheConnection, mut pkt: Incoming,
885    ) -> QuicResult<()> {
886        let recv_info = quiche::RecvInfo {
887            from: pkt.peer_addr,
888            to: pkt.local_addr,
889        };
890
891        if let Some(gro) = pkt.gro {
892            for dgram in pkt.buf.chunks_mut(gro as usize) {
893                qconn.recv(dgram, recv_info)?;
894            }
895        } else {
896            qconn.recv(&mut pkt.buf, recv_info)?;
897        }
898
899        Ok(())
900    }
901
902    // When a connection is established, process application data, if not the task
903    // is probably polled following a wakeup from boring, so we check if quiche
904    // has any handshake packets to send.
905    //
906    // TODO(erittenhouse): would be nice to decouple wait_for_data from the
907    // application, but wait_for_quiche relies on IOW methods, so we can't write a
908    // default implementation for ConnectionStage
909    //
910    // # Cancel safety
911    //
912    // This future is polled as an arm of the `select!` in [`Self::work_loop`],
913    // so it MUST be cancel safe: it may be dropped at any `.await` point when
914    // another arm completes first. It stays cancel safe because
915    // [`ApplicationOverQuic::wait_for_data`] is itself required to be cancel
916    // safe, and the handshake branch keeps no state across its `.await` beyond
917    // the local `send_buf` (which is returned to the free list or freed if the
918    // future is cancelled; any bytes gathered into it are re-gathered on the
919    // next poll). Take care to preserve this property when modifying it.
920    async fn wait_for_data_or_handshake<A: ApplicationOverQuic>(
921        &mut self, qconn: &mut QuicheConnection, quic_application: &mut A,
922    ) -> QuicResult<WaitForDataOrHandshakeDirective> {
923        if quic_application.should_act() {
924            // Poll the application to make progress.
925            //
926            // Once the connection has been established (i.e. the handshake is
927            // complete), we only poll the application.
928            //
929            // The exception is 0-RTT in TLS 1.3, where the full handshake is
930            // still in progress but we have 0-RTT keys to process early data.
931            // This means TLS callbacks might only be polled on the next timeout
932            // or when a packet is received from the peer.
933            quic_application.wait_for_data(qconn).await?;
934            Ok(WaitForDataOrHandshakeDirective::Noop)
935        } else {
936            // Poll quiche to make progress on handshake callbacks, gathering
937            // any handshake packets into an on-demand buffer that the caller
938            // flushes. `wait_for_quiche()` returns it only after generating a
939            // packet, so pending handshake waits do not retain a buffer.
940            let send_buf = self.wait_for_quiche(qconn).await?;
941            Ok(WaitForDataOrHandshakeDirective::Flush(send_buf))
942        }
943    }
944
945    /// Check if Quiche has any packets to send
946    ///
947    /// If yes: fills buffer and updates self.write_state.bytes_written
948    /// If no: Poll::Pending
949    ///
950    /// # Example
951    ///
952    /// This function can be used, for example, to drive an asynchronous TLS
953    /// handshake. Each call to `gather_data_from_quiche_conn` attempts to
954    /// progress the handshake via a call to `quiche::Connection.send()` -
955    /// once one of the `gather_data_from_quiche_conn()` calls writes to the
956    /// send buffer, we signal to the caller which has to take care of flushing
957    ///
958    /// # Cancel safety
959    ///
960    /// This future is awaited (indirectly) as an arm of the `select!` in
961    /// [`Self::work_loop`], so it MUST be cancel safe. The `poll_fn` below
962    /// holds no state across polls other than what lives in `self.write_state`,
963    /// so dropping the future between polls loses nothing: the next call simply
964    /// re-gathers. Take care to preserve this property when modifying it.
965    async fn wait_for_quiche(
966        &mut self, qconn: &mut QuicheConnection,
967    ) -> QuicResult<TransientSendBuf> {
968        let send_buf = std::future::poll_fn(|_| {
969            // Allocate inside this closure so a pending poll immediately
970            // returns its buffer to the pool instead of retaining it across
971            // the select! wait.
972            let mut send_buf =
973                TransientSendBuf::acquire(self.cfg.pool_send_buffer);
974
975            match self.gather_data_from_quiche_conn(
976                qconn,
977                send_buf.as_mut(),
978                true,
979            ) {
980                Ok(bytes_written) => {
981                    // We need to avoid consecutive calls to gather(), which write
982                    // data to the buffer, without a flush().
983                    // If we don't avoid those consecutive calls, we end
984                    // up overwriting data in the buffer or unnecessarily waiting
985                    // for more calls to drive_handshake()
986                    // before calling the handshake complete.
987                    if bytes_written == 0 && self.write_state.bytes_written == 0 {
988                        Poll::Pending
989                    } else {
990                        Poll::Ready(Ok(send_buf))
991                    }
992                },
993                _ => Poll::Ready(Err(quiche::Error::TlsFail)),
994            }
995        })
996        .await?;
997        Ok(send_buf)
998    }
999}
1000
1001/// Whether caller of [`wait_for_data_or_handshake`] is required to
1002/// call [`flush_buffer_to_socket`].
1003///
1004/// `Flush` carries the on-demand buffer the handshake data was gathered into so
1005/// the caller can flush it and then return it to the pool or drop it.
1006#[must_use]
1007enum WaitForDataOrHandshakeDirective {
1008    Noop,
1009    Flush(TransientSendBuf),
1010}
1011
1012pub struct Running<Tx, M, A> {
1013    pub(crate) params: IoWorkerParams<Tx, M>,
1014    pub(crate) context: ConnectionStageContext<A>,
1015    /// See [`QuicConnectionParams::quiche_conn`].
1016    pub(crate) qconn: Box<QuicheConnection>,
1017}
1018
1019impl<Tx, M, A> Running<Tx, M, A> {
1020    pub fn ssl(&mut self) -> &mut SslRef {
1021        // Deref to pick `Connection::as_mut` over `Box::as_mut`.
1022        (*self.qconn).as_mut()
1023    }
1024}
1025
1026pub(crate) struct Closing<Tx, M, A> {
1027    pub(crate) params: IoWorkerParams<Tx, M>,
1028    pub(crate) context: ConnectionStageContext<A>,
1029    pub(crate) work_loop_result: QuicResult<()>,
1030    /// See [`QuicConnectionParams::quiche_conn`].
1031    pub(crate) qconn: Box<QuicheConnection>,
1032}
1033
1034pub enum RunningOrClosing<Tx, M, A> {
1035    Running(Running<Tx, M, A>),
1036    Closing(Closing<Tx, M, A>),
1037}
1038
1039impl<Tx, M> IoWorker<Tx, M, Handshake>
1040where
1041    Tx: DatagramSocketSend + Send,
1042    M: Metrics,
1043{
1044    pub(crate) async fn run<A>(
1045        mut self, mut qconn: Box<QuicheConnection>,
1046        mut ctx: ConnectionStageContext<A>,
1047    ) -> RunningOrClosing<Tx, M, A>
1048    where
1049        A: ApplicationOverQuic,
1050    {
1051        // This makes an assumption that the waker being set in ex_data is stable
1052        // across the active task's lifetime. Moving a future that encompasses an
1053        // async callback from this task across a channel, for example, will
1054        // cause issues as this waker will then be stale and attempt to
1055        // wake the wrong task.
1056        std::future::poll_fn(|cx| {
1057            // Deref to pick `Connection::as_mut` over `Box::as_mut`.
1058            let ssl = (*qconn).as_mut();
1059            ssl.set_task_waker(Some(cx.waker().clone()));
1060
1061            Poll::Ready(())
1062        })
1063        .await;
1064
1065        #[cfg(target_os = "linux")]
1066        if let Some(incoming) = ctx.in_pkt.as_mut() {
1067            self.audit_log_stats
1068                .set_initial_so_mark_data(incoming.so_mark_data.take());
1069        }
1070
1071        let mut work_loop_result = self.work_loop(&mut qconn, &mut ctx).await;
1072        if work_loop_result.is_ok() && qconn.is_closed() {
1073            work_loop_result = Err(HandshakeError::ConnectionClosed.into());
1074        }
1075
1076        if let Err(err) = &work_loop_result {
1077            self.metrics.failed_handshakes(err.into()).inc();
1078
1079            return RunningOrClosing::Closing(Closing {
1080                params: self.into(),
1081                context: ctx,
1082                work_loop_result,
1083                qconn,
1084            });
1085        };
1086
1087        match self.on_conn_established(&mut qconn, &mut ctx.application) {
1088            Ok(()) => RunningOrClosing::Running(Running {
1089                params: self.into(),
1090                context: ctx,
1091                qconn,
1092            }),
1093            Err(e) => {
1094                foundations::telemetry::log::warn!(
1095                    "Handshake stage on_connection_established failed"; "error"=>%e
1096                );
1097
1098                RunningOrClosing::Closing(Closing {
1099                    params: self.into(),
1100                    context: ctx,
1101                    work_loop_result,
1102                    qconn,
1103                })
1104            },
1105        }
1106    }
1107
1108    fn on_conn_established<App: ApplicationOverQuic>(
1109        &mut self, qconn: &mut QuicheConnection, driver: &mut App,
1110    ) -> QuicResult<()> {
1111        // Only calculate the QUIC handshake duration and call the driver's
1112        // on_conn_established hook if this is the first time
1113        // is_established == true.
1114        if self.audit_log_stats.transport_handshake_duration_us() == -1 {
1115            self.conn_stage.handshake_info.set_elapsed();
1116            let handshake_info = &self.conn_stage.handshake_info;
1117
1118            self.audit_log_stats
1119                .set_transport_handshake_duration(handshake_info.elapsed());
1120
1121            driver.on_conn_established(qconn, handshake_info)?;
1122        }
1123
1124        if let Some(cid) = self.cfg.pending_cid.take() {
1125            self.unmap_cid(cid);
1126        }
1127
1128        Ok(())
1129    }
1130}
1131
1132impl<Tx, M, S> From<IoWorker<Tx, M, S>> for IoWorkerParams<Tx, M> {
1133    fn from(value: IoWorker<Tx, M, S>) -> Self {
1134        Self {
1135            socket: value.socket,
1136            shutdown_tx: value.shutdown_tx,
1137            cfg: value.cfg,
1138            audit_log_stats: value.audit_log_stats,
1139            write_state: value.write_state,
1140            conn_map_cmd_tx: value.conn_map_cmd_tx,
1141            cid_generator: value.cid_generator,
1142            #[cfg(feature = "perf-quic-listener-metrics")]
1143            init_rx_time: value.init_rx_time,
1144            metrics: value.metrics,
1145        }
1146    }
1147}
1148
1149impl<Tx, M> IoWorker<Tx, M, RunningApplication>
1150where
1151    Tx: DatagramSocketSend + Send,
1152    M: Metrics,
1153{
1154    pub(crate) async fn run<A: ApplicationOverQuic>(
1155        mut self, mut qconn: Box<QuicheConnection>,
1156        mut ctx: ConnectionStageContext<A>,
1157    ) -> Closing<Tx, M, A> {
1158        // Perform a single call to process_reads()/process_writes(),
1159        // unconditionally, to ensure that any application data (e.g.
1160        // STREAM frames or datagrams) processed by the Handshake
1161        // stage are properly passed to the application.
1162        if let Err(e) = self.conn_stage.on_read(true, &mut qconn, &mut ctx) {
1163            return Closing {
1164                params: self.into(),
1165                context: ctx,
1166                work_loop_result: Err(e),
1167                qconn,
1168            };
1169        };
1170
1171        let work_loop_result = self.work_loop(&mut qconn, &mut ctx).await;
1172
1173        Closing {
1174            params: self.into(),
1175            context: ctx,
1176            work_loop_result,
1177            qconn,
1178        }
1179    }
1180}
1181
1182impl<Tx, M> IoWorker<Tx, M, Close>
1183where
1184    Tx: DatagramSocketSend + Send,
1185    M: Metrics,
1186{
1187    pub(crate) async fn close<A: ApplicationOverQuic>(
1188        mut self, qconn: &mut QuicheConnection,
1189        ctx: &mut ConnectionStageContext<A>,
1190    ) {
1191        if self.conn_stage.work_loop_result.is_ok() &&
1192            self.bw_estimator.max_bandwidth > 0
1193        {
1194            let metrics = &self.metrics;
1195
1196            metrics
1197                .max_bandwidth_mbps()
1198                .observe(self.bw_estimator.max_bandwidth as f64 * 1e-6);
1199
1200            metrics
1201                .max_loss_pct()
1202                .observe(self.bw_estimator.max_loss_pct as f64 * 100.);
1203        }
1204
1205        if ctx.application.should_act() {
1206            ctx.application.on_conn_close(
1207                qconn,
1208                &self.metrics,
1209                &self.conn_stage.work_loop_result,
1210            );
1211        }
1212
1213        // TODO: this assumes that the tidy_up operation can be completed in one
1214        // send (ignoring flow/congestion control constraints). We should
1215        // guarantee that it gets sent by doublechecking the
1216        // gathered/flushed byte totals and retry if they don't match.
1217        //
1218        // This runs once per connection at close and sends a single
1219        // CONNECTION_CLOSE datagram, so acquire a buffer only for this send.
1220        let mut send_buf = TransientSendBuf::acquire(self.cfg.pool_send_buffer);
1221        let _ =
1222            self.gather_data_from_quiche_conn(qconn, send_buf.as_mut(), false);
1223        self.flush_buffer_to_socket(send_buf.as_ref()).await;
1224
1225        *ctx.stats.lock().unwrap() = QuicConnectionStats::from_conn(qconn);
1226
1227        if let Some(err) = qconn.peer_error() {
1228            if err.is_app {
1229                self.audit_log_stats
1230                    .set_recvd_conn_close_application_error_code(
1231                        err.error_code as _,
1232                    );
1233            } else {
1234                self.audit_log_stats
1235                    .set_recvd_conn_close_transport_error_code(
1236                        err.error_code as _,
1237                    );
1238            }
1239        }
1240
1241        if let Some(err) = qconn.local_error() {
1242            if err.is_app {
1243                self.audit_log_stats
1244                    .set_sent_conn_close_application_error_code(
1245                        err.error_code as _,
1246                    );
1247            } else {
1248                self.audit_log_stats
1249                    .set_sent_conn_close_transport_error_code(
1250                        err.error_code as _,
1251                    );
1252            }
1253        }
1254
1255        self.close_connection(qconn);
1256
1257        if let Err(work_loop_error) = self.conn_stage.work_loop_result {
1258            self.audit_log_stats
1259                .set_connection_close_reason(work_loop_error);
1260        }
1261    }
1262
1263    fn close_connection(&mut self, qconn: &mut QuicheConnection) {
1264        if let Some(cid) = self.cfg.pending_cid.take() {
1265            self.unmap_cid(cid);
1266        }
1267        while let Some(retired_cid) = qconn.retired_scid_next() {
1268            self.unmap_cid(retired_cid);
1269        }
1270        for cid in qconn.source_ids().cloned() {
1271            self.unmap_cid(cid.into_owned());
1272        }
1273
1274        self.metrics.connections_in_memory().dec();
1275    }
1276}
1277
1278/// Returns the minimum of `v1` and `v2`, ignoring `None`s.
1279fn min_of_some<T: Ord>(v1: Option<T>, v2: Option<T>) -> Option<T> {
1280    match (v1, v2) {
1281        (Some(a), Some(b)) => Some(a.min(b)),
1282        (Some(v), _) | (_, Some(v)) => Some(v),
1283        (None, None) => None,
1284    }
1285}
1286
1287/// A Token which increment the skipped_mid_handshake_flush_count metric on
1288/// `Drop` unless it is marked complete.
1289struct TrackMidHandshakeFlush<M: Metrics> {
1290    complete: bool,
1291    metrics: M,
1292}
1293
1294impl<M: Metrics> TrackMidHandshakeFlush<M> {
1295    fn new(metrics: M) -> Self {
1296        Self {
1297            complete: false,
1298            metrics,
1299        }
1300    }
1301
1302    fn mark_complete(&mut self) {
1303        self.complete = true;
1304    }
1305}
1306
1307impl<M: Metrics> Drop for TrackMidHandshakeFlush<M> {
1308    fn drop(&mut self) {
1309        if !self.complete {
1310            self.metrics.skipped_mid_handshake_flush_count().inc();
1311        }
1312    }
1313}
1314
1315fn random_u128() -> u128 {
1316    let mut buf = [0; 16];
1317    boring::rand::rand_bytes(&mut buf).expect("boring's RAND_bytes never fails");
1318    u128::from_ne_bytes(buf)
1319}
1320
1321#[cfg(test)]
1322mod pooled_send_buf_tests {
1323    use super::*;
1324
1325    // Each test runs on a freshly spawned thread so the thread-local
1326    // `SEND_BUF_POOL` starts empty (const-initialized) and cannot interfere
1327    // with other tests sharing the harness's worker threads.
1328
1329    #[test]
1330    fn caps_retained_buffers() {
1331        std::thread::spawn(|| {
1332            // Acquire more than the cap at once (all misses, so all fresh
1333            // allocations), then drop them. Only `SEND_BUF_POOL_CAP` may be
1334            // parked; the remainder are freed.
1335            let bufs: Vec<PooledSendBuf> = (0..SEND_BUF_POOL_CAP + 4)
1336                .map(|_| PooledSendBuf::acquire())
1337                .collect();
1338            drop(bufs);
1339
1340            let retained = SEND_BUF_POOL.with(|pool| pool.borrow().len());
1341            assert_eq!(retained, SEND_BUF_POOL_CAP);
1342        })
1343        .join()
1344        .unwrap();
1345    }
1346
1347    #[test]
1348    fn reuses_a_returned_buffer() {
1349        std::thread::spawn(|| {
1350            let first_ptr = {
1351                let buf = PooledSendBuf::acquire();
1352                assert_eq!(buf.len(), SEND_BUFFER_SIZE);
1353                buf.as_ptr()
1354            }; // returned to the pool here
1355
1356            let reused = PooledSendBuf::acquire();
1357            assert_eq!(
1358                reused.as_ptr(),
1359                first_ptr,
1360                "acquire should hand back the pooled allocation"
1361            );
1362        })
1363        .join()
1364        .unwrap();
1365    }
1366
1367    #[test]
1368    fn returns_to_the_dropping_thread() {
1369        // Acquire on one thread, drop on another: the buffer lands in the
1370        // dropping thread's pool (work-stealing migration across `.await` is
1371        // benign).
1372        let buf = std::thread::spawn(PooledSendBuf::acquire).join().unwrap();
1373
1374        std::thread::spawn(move || {
1375            assert_eq!(SEND_BUF_POOL.with(|pool| pool.borrow().len()), 0);
1376            drop(buf);
1377            assert_eq!(SEND_BUF_POOL.with(|pool| pool.borrow().len()), 1);
1378        })
1379        .join()
1380        .unwrap();
1381    }
1382}