1mod access;
163mod buffer;
164mod consumer;
165mod desc;
166mod event;
167pub mod msg;
168mod pool;
169mod producer;
170mod ring;
171
172#[cfg(all(test, loom))]
173mod concurrency;
174
175use core::num::NonZeroU16;
176
177pub use access::*;
178pub use buffer::*;
179pub use consumer::*;
180pub use desc::*;
181pub use event::*;
182pub use pool::*;
183pub use producer::*;
184pub use ring::*;
185use thiserror::Error;
186
187pub trait Notifier {
189 fn notify(&self, stats: QueueStats);
190}
191
192#[derive(Error, Debug)]
194pub enum VirtqError {
195 #[error("Ring error: {0}")]
196 RingError(RingError),
197 #[error("Allocation error: {0}")]
198 Alloc(AllocError),
199 #[error("Ring or pool temporarily full")]
200 Backpressure,
201 #[error("Allocation exceeds pool capacity")]
202 OutOfMemory,
203 #[error("Invalid chain received")]
204 BadChain,
205 #[error("Payload data too large: received {recv} bytes, limit {limit} bytes")]
206 PayloadTooLarge { recv: usize, limit: usize },
207 #[error("Reply data too large for allocated buffer")]
208 ReplyTooLarge,
209 #[error("Internal state error")]
210 InvalidState,
211 #[error("Memory write error")]
212 MemoryWriteError,
213 #[error("Memory read error")]
214 MemoryReadError,
215 #[error("No payload segment in this chain")]
216 NoPayloadSegment,
217}
218
219impl VirtqError {
220 #[inline(always)]
222 pub fn is_transient(&self) -> bool {
223 matches!(self, Self::Backpressure)
224 }
225}
226
227impl From<RingError> for VirtqError {
228 fn from(e: RingError) -> Self {
229 match e {
230 RingError::WouldBlock => Self::Backpressure,
231 other => Self::RingError(other),
232 }
233 }
234}
235
236impl From<AllocError> for VirtqError {
237 fn from(e: AllocError) -> Self {
238 match e {
239 AllocError::NoSpace => Self::Backpressure,
240 AllocError::OutOfMemory => Self::OutOfMemory,
241 other => Self::Alloc(other),
242 }
243 }
244}
245
246#[derive(Clone, Copy, Debug)]
259pub struct Layout {
260 desc_table_addr: u64,
262 desc_table_len: u16,
264 drv_evt_addr: u64,
266 dev_evt_addr: u64,
268}
269
270#[inline]
271const fn align_up(val: usize, align: usize) -> usize {
272 val.next_multiple_of(align)
273}
274
275impl Layout {
276 pub const unsafe fn from_base(base: u64, num_descs: NonZeroU16) -> Result<Self, RingError> {
287 let num_descs = num_descs.get() as usize;
288 if !num_descs.is_power_of_two() {
289 return Err(RingError::InvalidLayout);
290 }
291
292 if !base.is_multiple_of(Descriptor::ALIGN as u64) {
293 return Err(RingError::InvalidLayout);
294 }
295
296 if base
297 .checked_add(Layout::query_size(num_descs) as u64)
298 .is_none()
299 {
300 return Err(RingError::InvalidLayout);
301 }
302
303 let desc_size = num_descs * Descriptor::SIZE;
304 let event_size = EventSuppression::SIZE;
305 let event_align = EventSuppression::ALIGN;
306
307 let drv_evt_offset = align_up(desc_size, event_align);
308 let dev_evt_offset = align_up(drv_evt_offset + event_size, event_align);
309
310 Ok(Self {
311 desc_table_addr: base,
312 desc_table_len: num_descs as u16,
313 drv_evt_addr: base + drv_evt_offset as u64,
314 dev_evt_addr: base + dev_evt_offset as u64,
315 })
316 }
317
318 pub const fn desc_table_addr(&self) -> u64 {
320 self.desc_table_addr
321 }
322
323 pub const fn desc_table_len(&self) -> u16 {
325 self.desc_table_len
326 }
327
328 pub const fn drv_evt_addr(&self) -> u64 {
330 self.drv_evt_addr
331 }
332
333 pub const fn dev_evt_addr(&self) -> u64 {
335 self.dev_evt_addr
336 }
337
338 pub const fn query_size(num_descs: usize) -> usize {
341 let desc_size = num_descs * Descriptor::SIZE;
342 let event_size = EventSuppression::SIZE;
343 let event_align = EventSuppression::ALIGN;
344
345 let drv_evt_offset = align_up(desc_size, event_align);
347 let dev_evt_offset = align_up(drv_evt_offset + event_size, event_align);
348
349 dev_evt_offset + event_size
350 }
351}
352
353#[derive(Debug, Clone, Copy)]
358pub struct QueueStats {
359 pub num_free: usize,
361 pub num_inflight: usize,
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370pub enum SuppressionKind {
371 Enable,
373 Disable,
375 Descriptor(RingCursor),
377}
378
379#[derive(Copy, Clone, Debug, PartialEq, Eq)]
384pub struct Token {
385 pub seq: u32,
387 pub id: u16,
389}
390
391impl From<BufferElement> for Allocation {
392 fn from(value: BufferElement) -> Self {
393 Allocation {
394 addr: value.addr,
395 len: value.len as usize,
396 }
397 }
398}
399
400const _: () = {
401 #[allow(clippy::panic)]
402 #[allow(clippy::unwrap_used)]
403 const fn verify_layout(num_descs: usize) {
404 let base = 0x1000u64;
405
406 let layout =
408 match unsafe { Layout::from_base(base, NonZeroU16::new(num_descs as u16).unwrap()) } {
409 Ok(l) => l,
410 Err(_) => panic!("from_base failed"),
411 };
412
413 let expected_size = Layout::query_size(num_descs);
414
415 assert!(layout.desc_table_addr() == base);
416 assert!(layout.desc_table_len() as usize == num_descs);
417 assert!(
418 layout
419 .drv_evt_addr()
420 .is_multiple_of(EventSuppression::ALIGN as u64)
421 );
422 assert!(
423 layout
424 .dev_evt_addr()
425 .is_multiple_of(EventSuppression::ALIGN as u64)
426 );
427
428 let desc_end = base + (num_descs * Descriptor::SIZE) as u64;
430 assert!(layout.drv_evt_addr() >= desc_end);
431 assert!(layout.dev_evt_addr() >= layout.drv_evt_addr() + EventSuppression::SIZE as u64);
432
433 let layout_end = layout.dev_evt_addr() + EventSuppression::SIZE as u64;
435 assert!(base + expected_size as u64 == layout_end);
436 }
437
438 unsafe {
439 assert!(Layout::from_base(u64::MAX, NonZeroU16::new(1).unwrap()).is_err());
440 }
441
442 verify_layout(1);
443 verify_layout(2);
444 verify_layout(4);
445 verify_layout(8);
446 verify_layout(16);
447 verify_layout(32);
448 verify_layout(64);
449 verify_layout(128);
450 verify_layout(256);
451 verify_layout(512);
452 verify_layout(1024);
453};
454
455#[cfg(test)]
457pub(crate) mod test_utils {
458 use alloc::collections::BTreeMap;
459 use alloc::sync::Arc;
460 use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
461 use std::sync::Mutex;
462
463 use super::*;
464 use crate::virtq::ring::tests::{OwnedRing, TestMem};
465
466 #[derive(Debug, Clone)]
468 pub(crate) struct TestNotifier {
469 pub(crate) count: Arc<AtomicUsize>,
470 }
471
472 impl TestNotifier {
473 pub(crate) fn new() -> Self {
474 Self {
475 count: Arc::new(AtomicUsize::new(0)),
476 }
477 }
478
479 pub(crate) fn notification_count(&self) -> usize {
480 self.count.load(Ordering::Relaxed)
481 }
482 }
483
484 impl Notifier for TestNotifier {
485 fn notify(&self, _stats: QueueStats) {
486 self.count.fetch_add(1, Ordering::Relaxed);
487 }
488 }
489
490 #[derive(Clone)]
492 pub(crate) struct TestPool {
493 base: u64,
494 next: Arc<AtomicU64>,
495 size: usize,
496 max_alloc_len: usize,
497 allocations: Arc<Mutex<BTreeMap<u64, usize>>>,
498 }
499
500 impl TestPool {
501 pub(crate) fn new(base: u64, size: usize) -> Self {
502 Self {
503 base,
504 next: Arc::new(AtomicU64::new(base)),
505 size,
506 max_alloc_len: usize::MAX,
507 allocations: Arc::new(Mutex::new(BTreeMap::new())),
508 }
509 }
510
511 pub(crate) fn new_with_max_alloc_len(base: u64, size: usize, max_alloc_len: usize) -> Self {
512 Self {
513 base,
514 next: Arc::new(AtomicU64::new(base)),
515 size,
516 max_alloc_len,
517 allocations: Arc::new(Mutex::new(BTreeMap::new())),
518 }
519 }
520 }
521
522 impl BufferProvider for TestPool {
523 fn max_alloc_len(&self) -> usize {
524 self.max_alloc_len
525 }
526
527 fn alloc(&self, len: usize) -> Result<Allocation, AllocError> {
528 if len == 0 {
529 return Err(AllocError::InvalidArg);
530 }
531
532 let addr = self.next.fetch_add(len as u64, Ordering::Relaxed);
533 let end = addr + len as u64;
534 if end > self.base + self.size as u64 {
535 return Err(AllocError::NoSpace);
536 }
537 self.allocations
538 .lock()
539 .expect("poisoned mutex")
540 .insert(addr, len);
541 Ok(Allocation { addr, len })
542 }
543
544 fn dealloc(&self, addr: u64) -> Result<(), AllocError> {
545 self.allocations
546 .lock()
547 .expect("poisoned mutex")
548 .remove(&addr)
549 .map(|_| ())
550 .ok_or(AllocError::InvalidFree(addr, 0))
551 }
552 }
553
554 type TestProducer = VirtqProducer<TestMem, TestNotifier, TestPool>;
555 type TestConsumer = VirtqConsumer<TestMem, TestNotifier>;
556
557 pub(crate) fn make_test_producer(
560 ring: &OwnedRing,
561 ) -> (TestProducer, TestConsumer, TestNotifier) {
562 let layout = ring.layout();
563 let mem = ring.mem();
564
565 let pool_base = mem.base_addr() + Layout::query_size(ring.len()) as u64 + 0x100;
567 let pool = TestPool::new(pool_base, 0x8000);
568 let notifier = TestNotifier::new();
569
570 let producer = VirtqProducer::new(layout, mem.clone(), notifier.clone(), pool);
571 let consumer = VirtqConsumer::new(layout, mem, notifier.clone());
572
573 (producer, consumer, notifier)
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use alloc::sync::Arc;
580 use core::sync::atomic::{AtomicUsize, Ordering};
581
582 use super::*;
583 use crate::virtq::ring::tests::{TestMem, make_ring};
584 use crate::virtq::test_utils::*;
585
586 fn send_readwrite(
588 producer: &mut VirtqProducer<TestMem, TestNotifier, TestPool>,
589 entry_data: &[u8],
590 used_cap: usize,
591 ) -> Token {
592 let mut se = producer
593 .chain()
594 .readable(entry_data.len())
595 .writable(used_cap)
596 .build()
597 .unwrap();
598 se.write_all(entry_data).unwrap();
599 producer.submit(se).unwrap()
600 }
601
602 fn poll_received(
603 consumer: &mut VirtqConsumer<TestMem, TestNotifier>,
604 ) -> (RecvChain, ReplyChain<TestMem>) {
605 consumer.poll(1024).unwrap().unwrap()
606 }
607
608 #[test]
609 fn test_submit_notifies() {
610 let ring = make_ring(16);
611 let (mut producer, mut consumer, notifier) = make_test_producer(&ring);
612
613 let initial_count = notifier.notification_count();
614
615 let token = send_readwrite(&mut producer, b"hello", 64);
616 assert!(notifier.notification_count() > initial_count);
617
618 let (recv, _reply) = poll_received(&mut consumer);
619 assert_eq!(recv.token(), token);
620 }
621
622 #[test]
623 fn test_multiple_submits() {
624 let ring = make_ring(16);
625 let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
626
627 let tok1 = send_readwrite(&mut producer, b"request1", 64);
628 let tok2 = send_readwrite(&mut producer, b"request2", 64);
629 let tok3 = send_readwrite(&mut producer, b"request3", 64);
630
631 for _ in 0..3 {
633 let (_recv, reply) = poll_received(&mut consumer);
634 consumer.complete(reply).unwrap();
635 }
636
637 let used1 = producer.poll().unwrap().unwrap();
639 let used2 = producer.poll().unwrap().unwrap();
640 let used3 = producer.poll().unwrap().unwrap();
641 assert!(
642 [used1.token(), used2.token(), used3.token()].contains(&tok1)
643 && [used1.token(), used2.token(), used3.token()].contains(&tok2)
644 && [used1.token(), used2.token(), used3.token()].contains(&tok3)
645 );
646 }
647
648 #[test]
649 fn test_completion_batching_with_suppression() {
650 let ring = make_ring(16);
651 let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
652
653 let tok1 = send_readwrite(&mut producer, b"req1", 64);
655 let tok2 = send_readwrite(&mut producer, b"req2", 64);
656 let tok3 = send_readwrite(&mut producer, b"req3", 64);
657
658 let cursor = producer.used_cursor();
660 producer
661 .set_used_suppression(SuppressionKind::Descriptor(cursor))
662 .unwrap();
663
664 for _ in 0..3 {
666 let (_recv, reply) = poll_received(&mut consumer);
667 let ReplyChain::Writable(mut wc) = reply else {
668 panic!("expected writable reply");
669 };
670 wc.write_all(b"used-data").unwrap();
671 consumer.complete(wc).unwrap();
672 }
673
674 let mut responses = Vec::new();
676 producer
677 .drain(|reply| {
678 responses.push(reply.token());
679 })
680 .unwrap();
681
682 assert_eq!(responses.len(), 3);
683 assert!(responses.contains(&tok1));
684 assert!(responses.contains(&tok2));
685 assert!(responses.contains(&tok3));
686 }
687
688 #[test]
689 fn test_notifier_receives_context() {
690 #[derive(Debug, Clone)]
691 struct CtxNotifier {
692 last_num_free: Arc<AtomicUsize>,
693 last_num_inflight: Arc<AtomicUsize>,
694 count: Arc<AtomicUsize>,
695 }
696
697 impl Notifier for CtxNotifier {
698 fn notify(&self, stats: QueueStats) {
699 self.last_num_free.store(stats.num_free, Ordering::Relaxed);
700 self.last_num_inflight
701 .store(stats.num_inflight, Ordering::Relaxed);
702 self.count.fetch_add(1, Ordering::Relaxed);
703 }
704 }
705
706 let ring = make_ring(16);
707 let layout = ring.layout();
708 let mem = ring.mem();
709 let pool_base = mem.base_addr() + Layout::query_size(ring.len()) as u64 + 0x100;
710 let pool = TestPool::new(pool_base, 0x8000);
711 let notifier = CtxNotifier {
712 last_num_free: Arc::new(AtomicUsize::new(0)),
713 last_num_inflight: Arc::new(AtomicUsize::new(0)),
714 count: Arc::new(AtomicUsize::new(0)),
715 };
716
717 let mut producer = VirtqProducer::new(layout, mem, notifier.clone(), pool);
718
719 let mut se = producer.chain().readable(4).writable(32).build().unwrap();
720 se.write_all(b"test").unwrap();
721 producer.submit(se).unwrap();
722 assert_eq!(notifier.count.load(Ordering::Relaxed), 1);
723 assert!(notifier.last_num_inflight.load(Ordering::Relaxed) > 0);
724 }
725
726 #[test]
727 fn test_chain_batch() {
728 let ring = make_ring(16);
729 let (mut producer, mut consumer, notifier) = make_test_producer(&ring);
730
731 let initial_count = notifier.notification_count();
732
733 let mut se1 = producer.chain().readable(64).writable(128).build().unwrap();
735 se1.write_all(b"first-ent").unwrap();
736 let _tok1 = producer.submit(se1).unwrap();
737
738 let mut se2 = producer.chain().readable(64).writable(64).build().unwrap();
740 se2.write_all(b"copy-ent").unwrap();
741 let _tok2 = producer.submit(se2).unwrap();
742
743 let se3 = producer.chain().writable(32).build().unwrap();
745 let tok3 = producer.submit(se3).unwrap();
746
747 assert!(notifier.notification_count() > initial_count);
749
750 let (recv1, reply1) = poll_received(&mut consumer);
752 assert_eq!(recv1.to_bytes().as_ref(), b"first-ent");
753 consumer.complete(reply1).unwrap();
754
755 let (recv2, reply2) = poll_received(&mut consumer);
756 assert_eq!(recv2.to_bytes().as_ref(), b"copy-ent");
757 consumer.complete(reply2).unwrap();
758
759 let (_recv3, reply3) = poll_received(&mut consumer);
760 let ReplyChain::Writable(mut wc) = reply3 else {
761 panic!("expected writable reply");
762 };
763 wc.write_all(b"resp").unwrap();
764 consumer.complete(wc).unwrap();
765
766 let _ = producer.poll().unwrap().unwrap();
768 let _ = producer.poll().unwrap().unwrap();
769
770 let used = producer.poll().unwrap().unwrap();
771 assert_eq!(used.token(), tok3);
772 assert_eq!(used.to_bytes().unwrap().as_ref(), b"resp");
773 }
774
775 #[test]
776 fn test_chain_write_send() {
777 let ring = make_ring(16);
778 let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
779
780 let mut se = producer.chain().readable(64).writable(128).build().unwrap();
781 se.write_all(b"hello").unwrap();
782 let token = producer.submit(se).unwrap();
783
784 let (recv, reply) = poll_received(&mut consumer);
786 assert_eq!(recv.token(), token);
787 assert_eq!(recv.to_bytes().as_ref(), b"hello");
788
789 let ReplyChain::Writable(mut wc) = reply else {
791 panic!("expected writable reply");
792 };
793 wc.write_all(b"world").unwrap();
794 consumer.complete(wc).unwrap();
795 let used = producer.poll().unwrap().unwrap();
796 assert_eq!(used.to_bytes().unwrap().as_ref(), b"world");
797 }
798
799 #[test]
800 fn test_full_round_trip() {
801 let ring = make_ring(16);
802 let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
803
804 let token = send_readwrite(&mut producer, b"round-trip-recv", 128);
806
807 let (recv, reply) = poll_received(&mut consumer);
809 assert_eq!(recv.token(), token);
810 assert_eq!(recv.to_bytes().as_ref(), b"round-trip-recv");
811
812 let ReplyChain::Writable(mut wc) = reply else {
813 panic!("expected writable reply");
814 };
815 assert!(wc.capacity() >= 128);
816 wc.write_all(b"round-trip-rsp").unwrap();
817 consumer.complete(wc).unwrap();
818
819 let used = producer.poll().unwrap().unwrap();
821 assert_eq!(used.token(), token);
822 assert_eq!(used.to_bytes().unwrap().as_ref(), b"round-trip-rsp");
823 }
824
825 #[test]
826 fn test_cancel_submits_zero_length() {
827 let ring = make_ring(16);
828 let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
829
830 let token = send_readwrite(&mut producer, b"recv-data", 64);
831
832 let (_recv, reply) = poll_received(&mut consumer);
833 consumer.complete(reply).unwrap();
834
835 let used = producer.poll().unwrap().unwrap();
836 assert_eq!(used.token(), token);
837 assert_eq!(used.to_bytes().unwrap().len(), 0);
838 assert!(used.to_bytes().unwrap().is_empty());
839 }
840
841 #[test]
842 fn test_hold_reply_and_complete() {
843 let ring = make_ring(16);
844 let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
845
846 let token = send_readwrite(&mut producer, b"deferred", 64);
847
848 let (recv, reply) = poll_received(&mut consumer);
850 assert_eq!(recv.token(), token);
851 assert_eq!(recv.to_bytes().as_ref(), b"deferred");
852
853 let ReplyChain::Writable(mut wc) = reply else {
854 panic!("expected writable reply");
855 };
856 wc.write_all(b"deferred-used").unwrap();
857 consumer.complete(wc).unwrap();
858
859 let used = producer.poll().unwrap().unwrap();
860 assert_eq!(used.token(), token);
861 assert_eq!(used.to_bytes().unwrap().as_ref(), b"deferred-used");
862 }
863
864 #[test]
865 fn test_concurrent_pending_replies() {
866 let ring = make_ring(16);
867 let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
868
869 let tok1 = send_readwrite(&mut producer, b"first", 64);
870 let tok2 = send_readwrite(&mut producer, b"second", 64);
871
872 let (recv1, reply1) = poll_received(&mut consumer);
874 assert_eq!(recv1.token(), tok1);
875 assert_eq!(recv1.to_bytes().as_ref(), b"first");
876
877 let (recv2, reply2) = poll_received(&mut consumer);
878 assert_eq!(recv2.token(), tok2);
879 assert_eq!(recv2.to_bytes().as_ref(), b"second");
880
881 let ReplyChain::Writable(mut wc2) = reply2 else {
883 panic!("expected writable");
884 };
885 wc2.write_all(b"resp2").unwrap();
886 consumer.complete(wc2).unwrap();
887
888 let ReplyChain::Writable(mut wc1) = reply1 else {
889 panic!("expected writable");
890 };
891 wc1.write_all(b"resp1").unwrap();
892 consumer.complete(wc1).unwrap();
893
894 let used1 = producer.poll().unwrap().unwrap();
895 let used2 = producer.poll().unwrap().unwrap();
896 let mut responses: Vec<_> = vec![
897 (used1.token(), used1.to_bytes().unwrap().to_vec()),
898 (used2.token(), used2.to_bytes().unwrap().to_vec()),
899 ];
900 responses.sort_by_key(|(t, _)| t.seq);
901
902 let expected_first = responses.iter().find(|(t, _)| *t == tok1).unwrap();
903 let expected_second = responses.iter().find(|(t, _)| *t == tok2).unwrap();
904 assert_eq!(&expected_first.1[..], b"resp1");
905 assert_eq!(&expected_second.1[..], b"resp2");
906 }
907
908 fn send_readonly(
910 producer: &mut VirtqProducer<TestMem, TestNotifier, TestPool>,
911 entry_data: &[u8],
912 ) -> Token {
913 let mut se = producer.chain().readable(entry_data.len()).build().unwrap();
914 se.write_all(entry_data).unwrap();
915 producer.submit(se).unwrap()
916 }
917
918 #[test]
919 fn test_reclaim_frees_ring_slots() {
920 let ring = make_ring(4);
921 let (mut producer, mut consumer, _) = make_test_producer(&ring);
922
923 send_readonly(&mut producer, b"a");
925 send_readonly(&mut producer, b"b");
926 send_readonly(&mut producer, b"c");
927 send_readonly(&mut producer, b"d");
928
929 let mut se = producer.chain().readable(1).build().unwrap();
931 se.write_all(b"e").unwrap();
932 let res = producer.submit(se);
933 assert!(
934 matches!(res, Err(VirtqError::Backpressure)),
935 "expected Backpressure from full ring"
936 );
937
938 while let Some(result) = consumer.poll(1024).unwrap() {
940 let (_, reply) = result;
941 consumer.complete(reply).unwrap();
942 }
943
944 let count = producer.reclaim().unwrap();
946 assert_eq!(count, 4, "expected 4 reclaimed entries");
947
948 send_readonly(&mut producer, b"e");
950 }
951
952 #[test]
953 fn test_reclaim_buffers_rw_completions() {
954 let ring = make_ring(4);
955 let (mut producer, mut consumer, _) = make_test_producer(&ring);
956
957 let tok = send_readwrite(&mut producer, b"request", 64);
959
960 let (_, reply) = poll_received(&mut consumer);
962 let ReplyChain::Writable(mut wc) = reply else {
963 panic!("expected writable");
964 };
965 wc.write_all(b"response-data").unwrap();
966 consumer.complete(wc).unwrap();
967
968 let count = producer.reclaim().unwrap();
970 assert_eq!(count, 1);
971
972 let used = producer.poll().unwrap().unwrap();
974 assert_eq!(used.token(), tok);
975 assert_eq!(used.to_bytes().unwrap().as_ref(), b"response-data");
976 }
977
978 #[test]
979 fn test_reclaim_discards_readonly_completions() {
980 let ring = make_ring(8);
981 let (mut producer, mut consumer, _) = make_test_producer(&ring);
982
983 let _tok_ro1 = send_readonly(&mut producer, b"log1");
985 let tok_rw = send_readwrite(&mut producer, b"call", 64);
986 let _tok_ro2 = send_readonly(&mut producer, b"log2");
987
988 let (_, reply1) = poll_received(&mut consumer);
990 consumer.complete(reply1).unwrap(); let (_, reply2) = poll_received(&mut consumer);
993 let ReplyChain::Writable(mut wc) = reply2 else {
994 panic!("expected writable");
995 };
996 wc.write_all(b"result").unwrap();
997 consumer.complete(wc).unwrap(); let (_, reply3) = poll_received(&mut consumer);
1000 consumer.complete(reply3).unwrap(); let count = producer.reclaim().unwrap();
1004 assert_eq!(count, 3);
1005
1006 let used = producer.poll().unwrap().unwrap();
1008 assert_eq!(used.token(), tok_rw);
1009 assert_eq!(used.to_bytes().unwrap().as_ref(), b"result");
1010
1011 assert!(producer.poll().unwrap().is_none());
1013 }
1014
1015 #[test]
1016 fn test_reclaim_mixed_with_poll() {
1017 let ring = make_ring(8);
1018 let (mut producer, mut consumer, _) = make_test_producer(&ring);
1019
1020 send_readonly(&mut producer, b"x");
1022 let tok_rw = send_readwrite(&mut producer, b"y", 64);
1023
1024 let (_, reply1) = poll_received(&mut consumer);
1025 consumer.complete(reply1).unwrap();
1026
1027 let (_, reply2) = poll_received(&mut consumer);
1028 let ReplyChain::Writable(mut wc) = reply2 else {
1029 panic!("expected writable");
1030 };
1031 wc.write_all(b"reply").unwrap();
1032 consumer.complete(wc).unwrap();
1033
1034 let used1 = producer.poll().unwrap().unwrap();
1036 assert!(matches!(used1, UsedChain::Ack(_)));
1037
1038 let count = producer.reclaim().unwrap();
1040 assert_eq!(count, 1);
1041
1042 let used2 = producer.poll().unwrap().unwrap();
1044 assert_eq!(used2.token(), tok_rw);
1045 assert_eq!(used2.to_bytes().unwrap().as_ref(), b"reply");
1046 }
1047
1048 #[test]
1050 fn test_reclaim_submit_no_token_collision() {
1051 let ring = make_ring(8);
1052 let (mut producer, mut consumer, _) = make_test_producer(&ring);
1053
1054 let tok_old = send_readonly(&mut producer, b"log");
1056
1057 let (_, reply) = poll_received(&mut consumer);
1058 consumer.complete(reply).unwrap();
1059
1060 let count = producer.reclaim().unwrap();
1061 assert_eq!(count, 1);
1062
1063 let tok_new = send_readwrite(&mut producer, b"call", 64);
1065
1066 assert_ne!(
1068 tok_old, tok_new,
1069 "tokens must be unique across reclaim/submit cycles"
1070 );
1071
1072 let (_, reply) = poll_received(&mut consumer);
1074 let ReplyChain::Writable(mut wc) = reply else {
1075 panic!("expected writable");
1076 };
1077 wc.write_all(b"result").unwrap();
1078 consumer.complete(wc).unwrap();
1079
1080 let used = producer.poll().unwrap().unwrap();
1082 assert_eq!(used.token(), tok_new);
1083 assert_eq!(used.to_bytes().unwrap().as_ref(), b"result");
1084
1085 assert!(producer.poll().unwrap().is_none());
1087 }
1088
1089 #[test]
1091 fn test_reclaim_readonly_does_not_leak_pending() {
1092 let ring = make_ring(4);
1093 let (mut producer, mut consumer, _) = make_test_producer(&ring);
1094
1095 for _ in 0..10 {
1096 for _ in 0..4 {
1098 send_readonly(&mut producer, b"msg");
1099 }
1100
1101 while let Some(result) = consumer.poll(1024).unwrap() {
1103 let (_, reply) = result;
1104 consumer.complete(reply).unwrap();
1105 }
1106
1107 let count = producer.reclaim().unwrap();
1109 assert_eq!(count, 4);
1110
1111 assert!(
1113 producer.poll().unwrap().is_none(),
1114 "pending should be empty after reclaiming RO entries"
1115 );
1116 }
1117 }
1118}