Skip to main content

fips_core/transport/
packet_channel_io.rs

1impl PacketTx {
2    pub(crate) fn set_fast_ingress_sink(&mut self, sink: Arc<dyn PacketFastIngressSink>) {
3        self.fast_ingress = Some(sink);
4    }
5
6    pub(crate) fn try_fast_ingress_packet_batch(&self, batch: &mut PacketBatch) -> usize {
7        let Some(sink) = &self.fast_ingress else {
8            return 0;
9        };
10        sink.try_ingest_batch(&mut batch.packets)
11    }
12
13    pub(crate) fn packet_batch(&self, capacity: usize) -> PacketBatch {
14        self.batch_pool.take(capacity)
15    }
16
17    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
18    pub(crate) fn recv_buffer(&self, capacity: usize) -> Vec<u8> {
19        self.buffer_pool.take(capacity)
20    }
21
22    #[cfg(any(test, target_os = "linux", target_os = "macos"))]
23    pub(crate) fn packet_buffer(&self, data: Vec<u8>) -> PacketBuffer {
24        PacketBuffer::pooled(data, self.buffer_pool.clone())
25    }
26
27    pub fn send(
28        &self,
29        packet: ReceivedPacket,
30    ) -> Result<(), tokio::sync::mpsc::error::SendError<ReceivedPacket>> {
31        let tx = if packet.is_transport_priority() {
32            PacketQueueTx::Priority
33        } else {
34            PacketQueueTx::Bulk
35        };
36        self.send_item(tx, PacketQueueItem::One(packet))
37            .map_err(|item| match item {
38                PacketQueueItem::One(packet) => tokio::sync::mpsc::error::SendError(packet),
39                PacketQueueItem::Batch(_) => {
40                    unreachable!("single packet send cannot fail with a batch item")
41                }
42            })
43    }
44
45    pub(crate) fn send_packet_batch(&self, mut batch: PacketBatch) -> Result<(), ()> {
46        if batch.is_empty() {
47            return Ok(());
48        }
49
50        let packet_count = batch.packets.len();
51        let priority_count = batch
52            .packets
53            .iter()
54            .filter(|packet| packet.is_transport_priority())
55            .count();
56        if priority_count == 0 || priority_count == packet_count {
57            let tx = if priority_count == 0 {
58                PacketQueueTx::Bulk
59            } else {
60                PacketQueueTx::Priority
61            };
62            return self.send_packet_items(tx, batch);
63        }
64
65        let mut priority_packets = self.packet_batch(priority_count);
66        let mut bulk_packets = self.packet_batch(packet_count - priority_count);
67        for packet in batch.packets.drain(..) {
68            if packet.is_transport_priority() {
69                priority_packets.push(packet);
70            } else {
71                bulk_packets.push(packet);
72            }
73        }
74
75        self.send_packet_items(PacketQueueTx::Priority, priority_packets)?;
76        self.send_packet_items(PacketQueueTx::Bulk, bulk_packets)?;
77        Ok(())
78    }
79
80    fn send_packet_items(&self, tx: PacketQueueTx, packets: PacketBatch) -> Result<(), ()> {
81        if matches!(tx, PacketQueueTx::Bulk) {
82            return self.send_bulk_packet_items(packets);
83        }
84
85        let item = match packets.packets.len() {
86            0 => return Ok(()),
87            _ => PacketQueueItem::Batch(packets),
88        };
89        self.send_item(tx, item).map_err(|_| ())
90    }
91
92    fn send_bulk_packet_items(&self, mut packets: PacketBatch) -> Result<(), ()> {
93        let packet_count = packets.packets.len();
94        if packet_count == 0 {
95            return Ok(());
96        }
97
98        let granted = self.try_reserve_bulk_packet_prefix(packet_count);
99        if granted == 0 {
100            crate::perf_profile::record_event_count(
101                crate::perf_profile::Event::TransportBulkDropped,
102                packet_count as u64,
103            );
104            return Ok(());
105        }
106
107        if granted < packet_count {
108            let dropped = packet_count - granted;
109            let _dropped_tail = packets.packets.split_off(granted);
110            crate::perf_profile::record_event_count(
111                crate::perf_profile::Event::TransportBulkDropped,
112                dropped as u64,
113            );
114        }
115
116        let item = match packets.packets.len() {
117            0 => return Ok(()),
118            _ => PacketQueueItem::Batch(packets),
119        };
120        self.send_reserved_item(PacketQueueTx::Bulk, item, Some(granted))
121            .map_err(|_| ())
122    }
123
124    fn send_item(&self, tx: PacketQueueTx, item: PacketQueueItem) -> Result<(), PacketQueueItem> {
125        let packet_count = item.packet_count();
126        let bulk_reserved = if matches!(tx, PacketQueueTx::Bulk) && packet_count > 0 {
127            if !self.try_reserve_bulk_packets(packet_count) {
128                crate::perf_profile::record_event_count(
129                    crate::perf_profile::Event::TransportBulkDropped,
130                    packet_count as u64,
131                );
132                return Ok(());
133            }
134            Some(packet_count)
135        } else {
136            None
137        };
138        self.send_reserved_item(tx, item, bulk_reserved)
139    }
140
141    fn send_reserved_item(
142        &self,
143        tx: PacketQueueTx,
144        item: PacketQueueItem,
145        bulk_reserved: Option<usize>,
146    ) -> Result<(), PacketQueueItem> {
147        let packet_count = item.packet_count();
148        debug_assert_eq!(
149            bulk_reserved,
150            matches!(tx, PacketQueueTx::Bulk)
151                .then_some(packet_count)
152                .filter(|count| *count > 0)
153        );
154        let priority_reserved = matches!(tx, PacketQueueTx::Priority)
155            .then_some(packet_count)
156            .filter(|count| *count > 0);
157        if let Some(count) = priority_reserved {
158            self.priority_queued_packets.fetch_add(count, Relaxed);
159        }
160
161        let tracked_count = if self.track_backlog {
162            Some(packet_count)
163        } else {
164            None
165        };
166        let previous = tracked_count.map(|count| self.queued_packets.fetch_add(count, Relaxed));
167        match tx.try_send(self, item) {
168            Ok(()) => {
169                if let (Some(count), Some(previous)) = (tracked_count, previous) {
170                    let queued = previous.saturating_add(count);
171                    if previous < TRANSPORT_CHANNEL_BACKLOG_HIGH_WATER
172                        && queued >= TRANSPORT_CHANNEL_BACKLOG_HIGH_WATER
173                    {
174                        crate::perf_profile::record_event(
175                            crate::perf_profile::Event::TransportChannelBacklogHigh,
176                        );
177                    }
178                }
179                Ok(())
180            }
181            Err(PacketSendFailure::Closed(item)) => {
182                if let Some(count) = tracked_count {
183                    self.queued_packets.fetch_sub(count, Relaxed);
184                }
185                if let Some(count) = priority_reserved {
186                    release_priority_packets(&self.priority_queued_packets, count);
187                }
188                if let Some(count) = bulk_reserved {
189                    self.release_bulk_packets(count);
190                }
191                Err(item)
192            }
193            Err(PacketSendFailure::DroppedBulk(dropped_count)) => {
194                if let Some(count) = tracked_count {
195                    self.queued_packets.fetch_sub(count, Relaxed);
196                }
197                if let Some(count) = priority_reserved {
198                    release_priority_packets(&self.priority_queued_packets, count);
199                }
200                if let Some(count) = bulk_reserved {
201                    self.release_bulk_packets(count);
202                }
203                crate::perf_profile::record_event_count(
204                    crate::perf_profile::Event::TransportBulkDropped,
205                    dropped_count as u64,
206                );
207                Ok(())
208            }
209        }
210    }
211
212    fn try_reserve_bulk_packets(&self, count: usize) -> bool {
213        self.bulk_queued_packets
214            .fetch_update(Relaxed, Relaxed, |current| {
215                current
216                    .checked_add(count)
217                    .filter(|next| *next <= self.bulk_packet_capacity)
218            })
219            .is_ok()
220    }
221
222    fn try_reserve_bulk_packet_prefix(&self, requested: usize) -> usize {
223        if requested == 0 {
224            return 0;
225        }
226
227        let mut current = self.bulk_queued_packets.load(Relaxed);
228        loop {
229            let available = self.bulk_packet_capacity.saturating_sub(current);
230            let granted = requested.min(available);
231            if granted == 0 {
232                return 0;
233            }
234            match self.bulk_queued_packets.compare_exchange_weak(
235                current,
236                current + granted,
237                Relaxed,
238                Relaxed,
239            ) {
240                Ok(_) => return granted,
241                Err(actual) => current = actual,
242            }
243        }
244    }
245
246    fn release_bulk_packets(&self, count: usize) {
247        release_reserved_bulk_packets(&self.bulk_queued_packets, count);
248    }
249}
250
251impl PacketRx {
252    pub(crate) fn priority_queued_packets(&self) -> usize {
253        self.priority_queued_packets.load(Relaxed)
254    }
255
256    pub(crate) fn priority_ready_packets(&self) -> usize {
257        self.pending_priority
258            .as_ref()
259            .map_or(0, |packets| packets.batch.packets.len())
260            .saturating_add(self.priority_queued_packets())
261    }
262
263    pub async fn recv(&mut self) -> Option<ReceivedPacket> {
264        loop {
265            match self.try_recv() {
266                Ok(packet) => return Some(packet),
267                Err(TryRecvError::Disconnected) => return None,
268                Err(TryRecvError::Empty) => {}
269            }
270
271            tokio::select! {
272                biased;
273                item = self.priority.recv(), if !self.priority_closed => {
274                    match item {
275                        Some(item) => {
276                            if let Some(packet) = self.packet_from_item(item, PacketLane::Priority) {
277                                return Some(packet);
278                            }
279                        }
280                        None => self.priority_closed = true,
281                    }
282                }
283                item = self.bulk.recv(), if !self.bulk_closed => {
284                    match item {
285                        Some(item) => {
286                            if let Some(packet) = self.packet_from_item(item, PacketLane::Bulk) {
287                                return Some(packet);
288                            }
289                        }
290                        None => self.bulk_closed = true,
291                    }
292                }
293            }
294        }
295    }
296
297    pub fn try_recv(&mut self) -> Result<ReceivedPacket, TryRecvError> {
298        if let Some(packet) = Self::take_pending(&mut self.pending_priority) {
299            return Ok(packet);
300        }
301
302        if self.should_probe_priority() {
303            match self.priority.try_recv() {
304                Ok(item) => {
305                    if let Some(packet) = self.packet_from_item(item, PacketLane::Priority) {
306                        return Ok(packet);
307                    }
308                }
309                Err(TryRecvError::Empty) => {}
310                Err(TryRecvError::Disconnected) => {
311                    self.priority_closed = true;
312                }
313            }
314        }
315
316        if let Some(packet) = Self::take_pending(&mut self.pending_bulk) {
317            return Ok(packet);
318        }
319
320        match self.bulk.try_recv() {
321            Ok(item) => self
322                .packet_from_item(item, PacketLane::Bulk)
323                .ok_or(TryRecvError::Empty),
324            Err(TryRecvError::Empty) => {
325                if self.priority_closed && self.bulk_closed {
326                    Err(TryRecvError::Disconnected)
327                } else {
328                    Err(TryRecvError::Empty)
329                }
330            }
331            Err(TryRecvError::Disconnected) => {
332                self.bulk_closed = true;
333                if self.priority_closed {
334                    Err(TryRecvError::Disconnected)
335                } else {
336                    Err(TryRecvError::Empty)
337                }
338            }
339        }
340    }
341
342    pub(crate) fn drain_ready<F>(&mut self, limit: usize, mut consume: F) -> usize
343    where
344        F: FnMut(ReceivedPacket) -> bool,
345    {
346        let mut drained = 0usize;
347        while drained < limit {
348            if !self.drain_pending_priority(limit, &mut drained, &mut consume) {
349                break;
350            }
351            if drained >= limit {
352                break;
353            }
354
355            if self.should_probe_priority() {
356                match self.priority.try_recv() {
357                    Ok(item) => {
358                        if !self.drain_item(
359                            item,
360                            PacketLane::Priority,
361                            limit,
362                            &mut drained,
363                            &mut consume,
364                        ) {
365                            break;
366                        }
367                        continue;
368                    }
369                    Err(TryRecvError::Empty) => {}
370                    Err(TryRecvError::Disconnected) => {
371                        self.priority_closed = true;
372                    }
373                }
374            }
375            if drained >= limit {
376                break;
377            }
378
379            if !self.drain_pending_bulk(limit, &mut drained, &mut consume) {
380                break;
381            }
382            if drained >= limit {
383                break;
384            }
385
386            match self.bulk.try_recv() {
387                Ok(item) => {
388                    if !self.drain_item(item, PacketLane::Bulk, limit, &mut drained, &mut consume) {
389                        break;
390                    }
391                }
392                Err(TryRecvError::Empty) => break,
393                Err(TryRecvError::Disconnected) => {
394                    self.bulk_closed = true;
395                    break;
396                }
397            }
398        }
399        drained
400    }
401
402    fn packet_from_item(
403        &mut self,
404        item: PacketQueueItem,
405        lane: PacketLane,
406    ) -> Option<ReceivedPacket> {
407        item.record_dequeue_wait(lane);
408        let packet_count = item.packet_count();
409        if self.track_backlog {
410            self.queued_packets.fetch_sub(packet_count, Relaxed);
411        }
412        if matches!(lane, PacketLane::Priority) {
413            release_priority_packets(&self.priority_queued_packets, packet_count);
414        }
415        if matches!(lane, PacketLane::Bulk) {
416            release_reserved_bulk_packets(&self.bulk_queued_packets, packet_count);
417        }
418        let rx_loop_owned_at = crate::perf_profile::stamp();
419        match item {
420            PacketQueueItem::One(mut packet) => {
421                packet.trace_rx_loop_owned_at = rx_loop_owned_at;
422                Some(packet)
423            }
424            PacketQueueItem::Batch(packets) => {
425                let mut pending = PendingPackets::new(packets, rx_loop_owned_at);
426                let packet = pending.next()?;
427                if !pending.batch.packets.is_empty() {
428                    match lane {
429                        PacketLane::Priority => self.pending_priority = Some(pending),
430                        PacketLane::Bulk => self.pending_bulk = Some(pending),
431                    }
432                }
433                Some(packet)
434            }
435        }
436    }
437
438    fn drain_item<F>(
439        &mut self,
440        item: PacketQueueItem,
441        lane: PacketLane,
442        limit: usize,
443        drained: &mut usize,
444        consume: &mut F,
445    ) -> bool
446    where
447        F: FnMut(ReceivedPacket) -> bool,
448    {
449        if let Some(packet) = self.packet_from_item(item, lane) {
450            *drained += 1;
451            if !consume(packet) {
452                return false;
453            }
454        }
455
456        match lane {
457            PacketLane::Priority => self.drain_pending_priority(limit, drained, consume),
458            PacketLane::Bulk => self.drain_pending_bulk(limit, drained, consume),
459        }
460    }
461
462    fn drain_pending_priority<F>(
463        &mut self,
464        limit: usize,
465        drained: &mut usize,
466        consume: &mut F,
467    ) -> bool
468    where
469        F: FnMut(ReceivedPacket) -> bool,
470    {
471        while *drained < limit {
472            let Some(packet) = Self::take_pending(&mut self.pending_priority) else {
473                return true;
474            };
475            *drained += 1;
476            if !consume(packet) {
477                return false;
478            }
479        }
480        true
481    }
482
483    fn drain_pending_bulk<F>(&mut self, limit: usize, drained: &mut usize, consume: &mut F) -> bool
484    where
485        F: FnMut(ReceivedPacket) -> bool,
486    {
487        while *drained < limit {
488            if self.should_probe_priority() {
489                return true;
490            }
491            let Some(packet) = Self::take_pending(&mut self.pending_bulk) else {
492                return true;
493            };
494            *drained += 1;
495            if !consume(packet) {
496                return false;
497            }
498        }
499        true
500    }
501
502    fn should_probe_priority(&self) -> bool {
503        !self.priority_closed
504            && (self.priority_queued_packets.load(Relaxed) > 0 || self.bulk_closed)
505    }
506
507    fn take_pending(pending: &mut Option<PendingPackets>) -> Option<ReceivedPacket> {
508        let packets = pending.as_mut()?;
509        let packet = packets.next();
510        if packets.batch.packets.is_empty() {
511            *pending = None;
512        }
513        packet
514    }
515}
516
517#[inline]
518fn packet_channel_tracks_backlog() -> bool {
519    cfg!(test) || crate::perf_profile::enabled()
520}
521
522fn release_reserved_bulk_packets(counter: &AtomicUsize, count: usize) {
523    if count == 0 {
524        return;
525    }
526
527    let previous = counter.fetch_sub(count, Relaxed);
528    debug_assert!(
529        previous >= count,
530        "transport bulk queued packet accounting underflow"
531    );
532}
533
534fn release_priority_packets(counter: &AtomicUsize, count: usize) {
535    if count == 0 {
536        return;
537    }
538
539    let previous = counter.fetch_sub(count, Relaxed);
540    debug_assert!(
541        previous >= count,
542        "transport priority queued packet accounting underflow"
543    );
544}
545
546/// Create a packet channel.
547///
548/// The capacity applies to bulk packets. Priority traffic is intentionally
549/// unbounded so small control-shaped packets can still wake the rx loop while a
550/// bulk receiver is saturated.
551pub fn packet_channel(buffer: usize) -> (PacketTx, PacketRx) {
552    let (priority_tx, priority_rx) = tokio::sync::mpsc::unbounded_channel();
553    let (bulk_tx, bulk_rx) = tokio::sync::mpsc::channel(buffer.max(1));
554    let priority_queued_packets = Arc::new(AtomicUsize::new(0));
555    let queued_packets = Arc::new(AtomicUsize::new(0));
556    let bulk_queued_packets = Arc::new(AtomicUsize::new(0));
557    let track_backlog = packet_channel_tracks_backlog();
558    (
559        PacketTx {
560            priority: priority_tx,
561            bulk: bulk_tx,
562            fast_ingress: None,
563            batch_pool: PacketBatchPool::new(),
564            #[cfg(any(test, target_os = "linux", target_os = "macos"))]
565            buffer_pool: PacketBufferPool::new(),
566            priority_queued_packets: Arc::clone(&priority_queued_packets),
567            queued_packets: Arc::clone(&queued_packets),
568            bulk_queued_packets: Arc::clone(&bulk_queued_packets),
569            bulk_packet_capacity: buffer.max(1),
570            track_backlog,
571        },
572        PacketRx {
573            priority: priority_rx,
574            bulk: bulk_rx,
575            priority_queued_packets,
576            queued_packets,
577            bulk_queued_packets,
578            track_backlog,
579            pending_priority: None,
580            pending_bulk: None,
581            priority_closed: false,
582            bulk_closed: false,
583        },
584    )
585}
586
587#[cfg(test)]
588#[path = "packet_channel/tests.rs"]
589mod tests;