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