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