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    #[cfg(test)]
253    pub(crate) fn queued_packets_for_test(&self) -> usize {
254        self.pending_priority
255            .as_ref()
256            .map_or(0, |packets| packets.batch.packets.len())
257            .saturating_add(
258                self.pending_bulk
259                    .as_ref()
260                    .map_or(0, |packets| packets.batch.packets.len()),
261            )
262            .saturating_add(self.queued_packets.load(Relaxed))
263    }
264
265    pub(crate) fn priority_queued_packets(&self) -> usize {
266        self.priority_queued_packets.load(Relaxed)
267    }
268
269    pub(crate) fn priority_ready_packets(&self) -> usize {
270        self.pending_priority
271            .as_ref()
272            .map_or(0, |packets| packets.batch.packets.len())
273            .saturating_add(self.priority_queued_packets())
274    }
275
276    pub async fn recv(&mut self) -> Option<ReceivedPacket> {
277        loop {
278            match self.try_recv() {
279                Ok(packet) => return Some(packet),
280                Err(TryRecvError::Disconnected) => return None,
281                Err(TryRecvError::Empty) => {}
282            }
283
284            tokio::select! {
285                biased;
286                item = self.priority.recv(), if !self.priority_closed => {
287                    match item {
288                        Some(item) => {
289                            if let Some(packet) = self.packet_from_item(item, PacketLane::Priority) {
290                                return Some(packet);
291                            }
292                        }
293                        None => self.priority_closed = true,
294                    }
295                }
296                item = self.bulk.recv(), if !self.bulk_closed => {
297                    match item {
298                        Some(item) => {
299                            if let Some(packet) = self.packet_from_item(item, PacketLane::Bulk) {
300                                return Some(packet);
301                            }
302                        }
303                        None => self.bulk_closed = true,
304                    }
305                }
306            }
307        }
308    }
309
310    pub fn try_recv(&mut self) -> Result<ReceivedPacket, TryRecvError> {
311        if let Some(packet) = Self::take_pending(&mut self.pending_priority) {
312            return Ok(packet);
313        }
314
315        if self.should_probe_priority() {
316            match self.priority.try_recv() {
317                Ok(item) => {
318                    if let Some(packet) = self.packet_from_item(item, PacketLane::Priority) {
319                        return Ok(packet);
320                    }
321                }
322                Err(TryRecvError::Empty) => {}
323                Err(TryRecvError::Disconnected) => {
324                    self.priority_closed = true;
325                }
326            }
327        }
328
329        if let Some(packet) = Self::take_pending(&mut self.pending_bulk) {
330            return Ok(packet);
331        }
332
333        match self.bulk.try_recv() {
334            Ok(item) => self
335                .packet_from_item(item, PacketLane::Bulk)
336                .ok_or(TryRecvError::Empty),
337            Err(TryRecvError::Empty) => {
338                if self.priority_closed && self.bulk_closed {
339                    Err(TryRecvError::Disconnected)
340                } else {
341                    Err(TryRecvError::Empty)
342                }
343            }
344            Err(TryRecvError::Disconnected) => {
345                self.bulk_closed = true;
346                if self.priority_closed {
347                    Err(TryRecvError::Disconnected)
348                } else {
349                    Err(TryRecvError::Empty)
350                }
351            }
352        }
353    }
354
355    pub(crate) fn drain_ready<F>(&mut self, limit: usize, mut consume: F) -> usize
356    where
357        F: FnMut(ReceivedPacket) -> bool,
358    {
359        let mut drained = 0usize;
360        while drained < limit {
361            if !self.drain_pending_priority(limit, &mut drained, &mut consume) {
362                break;
363            }
364            if drained >= limit {
365                break;
366            }
367
368            if self.should_probe_priority() {
369                match self.priority.try_recv() {
370                    Ok(item) => {
371                        if !self.drain_item(
372                            item,
373                            PacketLane::Priority,
374                            limit,
375                            &mut drained,
376                            &mut consume,
377                        ) {
378                            break;
379                        }
380                        continue;
381                    }
382                    Err(TryRecvError::Empty) => {}
383                    Err(TryRecvError::Disconnected) => {
384                        self.priority_closed = true;
385                    }
386                }
387            }
388            if drained >= limit {
389                break;
390            }
391
392            if !self.drain_pending_bulk(limit, &mut drained, &mut consume) {
393                break;
394            }
395            if drained >= limit {
396                break;
397            }
398
399            match self.bulk.try_recv() {
400                Ok(item) => {
401                    if !self.drain_item(item, PacketLane::Bulk, limit, &mut drained, &mut consume) {
402                        break;
403                    }
404                }
405                Err(TryRecvError::Empty) => break,
406                Err(TryRecvError::Disconnected) => {
407                    self.bulk_closed = true;
408                    break;
409                }
410            }
411        }
412        drained
413    }
414
415    fn packet_from_item(
416        &mut self,
417        item: PacketQueueItem,
418        lane: PacketLane,
419    ) -> Option<ReceivedPacket> {
420        item.record_dequeue_wait(lane);
421        let packet_count = item.packet_count();
422        if self.track_backlog {
423            self.queued_packets.fetch_sub(packet_count, Relaxed);
424        }
425        if matches!(lane, PacketLane::Priority) {
426            release_priority_packets(&self.priority_queued_packets, packet_count);
427        }
428        if matches!(lane, PacketLane::Bulk) {
429            release_reserved_bulk_packets(&self.bulk_queued_packets, packet_count);
430        }
431        let rx_loop_owned_at = crate::perf_profile::stamp();
432        match item {
433            PacketQueueItem::One(mut packet) => {
434                packet.trace_rx_loop_owned_at = rx_loop_owned_at;
435                Some(packet)
436            }
437            PacketQueueItem::Batch(packets) => {
438                let mut pending = PendingPackets::new(packets, rx_loop_owned_at);
439                let packet = pending.next()?;
440                if !pending.batch.packets.is_empty() {
441                    match lane {
442                        PacketLane::Priority => self.pending_priority = Some(pending),
443                        PacketLane::Bulk => self.pending_bulk = Some(pending),
444                    }
445                }
446                Some(packet)
447            }
448        }
449    }
450
451    fn drain_item<F>(
452        &mut self,
453        item: PacketQueueItem,
454        lane: PacketLane,
455        limit: usize,
456        drained: &mut usize,
457        consume: &mut F,
458    ) -> bool
459    where
460        F: FnMut(ReceivedPacket) -> bool,
461    {
462        if let Some(packet) = self.packet_from_item(item, lane) {
463            *drained += 1;
464            if !consume(packet) {
465                return false;
466            }
467        }
468
469        match lane {
470            PacketLane::Priority => self.drain_pending_priority(limit, drained, consume),
471            PacketLane::Bulk => self.drain_pending_bulk(limit, drained, consume),
472        }
473    }
474
475    fn drain_pending_priority<F>(
476        &mut self,
477        limit: usize,
478        drained: &mut usize,
479        consume: &mut F,
480    ) -> bool
481    where
482        F: FnMut(ReceivedPacket) -> bool,
483    {
484        while *drained < limit {
485            let Some(packet) = Self::take_pending(&mut self.pending_priority) else {
486                return true;
487            };
488            *drained += 1;
489            if !consume(packet) {
490                return false;
491            }
492        }
493        true
494    }
495
496    fn drain_pending_bulk<F>(&mut self, limit: usize, drained: &mut usize, consume: &mut F) -> bool
497    where
498        F: FnMut(ReceivedPacket) -> bool,
499    {
500        while *drained < limit {
501            if self.should_probe_priority() {
502                return true;
503            }
504            let Some(packet) = Self::take_pending(&mut self.pending_bulk) else {
505                return true;
506            };
507            *drained += 1;
508            if !consume(packet) {
509                return false;
510            }
511        }
512        true
513    }
514
515    fn should_probe_priority(&self) -> bool {
516        !self.priority_closed
517            && (self.priority_queued_packets.load(Relaxed) > 0 || self.bulk_closed)
518    }
519
520    fn take_pending(pending: &mut Option<PendingPackets>) -> Option<ReceivedPacket> {
521        let packets = pending.as_mut()?;
522        let packet = packets.next();
523        if packets.batch.packets.is_empty() {
524            *pending = None;
525        }
526        packet
527    }
528}
529
530#[inline]
531fn packet_channel_tracks_backlog() -> bool {
532    cfg!(test) || crate::perf_profile::enabled()
533}
534
535fn release_reserved_bulk_packets(counter: &AtomicUsize, count: usize) {
536    if count == 0 {
537        return;
538    }
539
540    let previous = counter.fetch_sub(count, Relaxed);
541    debug_assert!(
542        previous >= count,
543        "transport bulk queued packet accounting underflow"
544    );
545}
546
547fn release_priority_packets(counter: &AtomicUsize, count: usize) {
548    if count == 0 {
549        return;
550    }
551
552    let previous = counter.fetch_sub(count, Relaxed);
553    debug_assert!(
554        previous >= count,
555        "transport priority queued packet accounting underflow"
556    );
557}
558
559/// Create a packet channel.
560///
561/// The capacity applies to bulk packets. Priority traffic is intentionally
562/// unbounded so small control-shaped packets can still wake the rx loop while a
563/// bulk receiver is saturated.
564pub fn packet_channel(buffer: usize) -> (PacketTx, PacketRx) {
565    let (priority_tx, priority_rx) = tokio::sync::mpsc::unbounded_channel();
566    let (bulk_tx, bulk_rx) = tokio::sync::mpsc::channel(buffer.max(1));
567    let priority_queued_packets = Arc::new(AtomicUsize::new(0));
568    let queued_packets = Arc::new(AtomicUsize::new(0));
569    let bulk_queued_packets = Arc::new(AtomicUsize::new(0));
570    let track_backlog = packet_channel_tracks_backlog();
571    (
572        PacketTx {
573            priority: priority_tx,
574            bulk: bulk_tx,
575            fast_ingress: None,
576            batch_pool: PacketBatchPool::new(),
577            #[cfg(any(test, target_os = "linux", target_os = "macos"))]
578            buffer_pool: PacketBufferPool::new(),
579            priority_queued_packets: Arc::clone(&priority_queued_packets),
580            queued_packets: Arc::clone(&queued_packets),
581            bulk_queued_packets: Arc::clone(&bulk_queued_packets),
582            bulk_packet_capacity: buffer.max(1),
583            track_backlog,
584        },
585        PacketRx {
586            priority: priority_rx,
587            bulk: bulk_rx,
588            priority_queued_packets,
589            queued_packets,
590            bulk_queued_packets,
591            track_backlog,
592            pending_priority: None,
593            pending_bulk: None,
594            priority_closed: false,
595            bulk_closed: false,
596        },
597    )
598}
599
600#[cfg(test)]
601#[path = "packet_channel/tests.rs"]
602mod tests;