@@ -181,9 +181,13 @@ pub trait Notifier {
181181#[ derive( Error , Debug ) ]
182182pub enum VirtqError {
183183 #[ error( "Ring error: {0}" ) ]
184- RingError ( # [ from ] RingError ) ,
184+ RingError ( RingError ) ,
185185 #[ error( "Allocation error: {0}" ) ]
186- Alloc ( #[ from] AllocError ) ,
186+ Alloc ( AllocError ) ,
187+ #[ error( "Ring or pool temporarily full" ) ]
188+ Backpressure ,
189+ #[ error( "Allocation exceeds pool capacity" ) ]
190+ OutOfMemory ,
187191 #[ error( "Invalid token" ) ]
188192 BadToken ,
189193 #[ error( "Invalid chain received" ) ]
@@ -202,6 +206,33 @@ pub enum VirtqError {
202206 NoReadableBuffer ,
203207}
204208
209+ impl VirtqError {
210+ /// Check if this error is transient or unrecoverable.
211+ #[ inline( always) ]
212+ pub fn is_transient ( & self ) -> bool {
213+ matches ! ( self , Self :: Backpressure )
214+ }
215+ }
216+
217+ impl From < RingError > for VirtqError {
218+ fn from ( e : RingError ) -> Self {
219+ match e {
220+ RingError :: WouldBlock => Self :: Backpressure ,
221+ other => Self :: RingError ( other) ,
222+ }
223+ }
224+ }
225+
226+ impl From < AllocError > for VirtqError {
227+ fn from ( e : AllocError ) -> Self {
228+ match e {
229+ AllocError :: NoSpace => Self :: Backpressure ,
230+ AllocError :: OutOfMemory => Self :: OutOfMemory ,
231+ other => Self :: Alloc ( other) ,
232+ }
233+ }
234+ }
235+
205236/// Layout of a packed virtqueue ring in shared memory.
206237///
207238/// Describes the memory addresses for the descriptor table and event suppression
@@ -424,7 +455,7 @@ pub(crate) mod test_utils {
424455 let addr = self . next . fetch_add ( len as u64 , Ordering :: Relaxed ) ;
425456 let end = addr + len as u64 ;
426457 if end > self . base + self . size as u64 {
427- return Err ( AllocError :: OutOfMemory ) ;
458+ return Err ( AllocError :: NoSpace ) ;
428459 }
429460 Ok ( Allocation { addr, len } )
430461 }
@@ -794,6 +825,153 @@ mod tests {
794825 assert_eq ! ( & expected_first. 1 [ ..] , b"resp1" ) ;
795826 assert_eq ! ( & expected_second. 1 [ ..] , b"resp2" ) ;
796827 }
828+
829+ /// Helper: submit a ReadOnly entry (entry data, no completion).
830+ fn send_readonly (
831+ producer : & mut VirtqProducer < TestMem , TestNotifier , TestPool > ,
832+ entry_data : & [ u8 ] ,
833+ ) -> Token {
834+ let mut se = producer. chain ( ) . entry ( entry_data. len ( ) ) . build ( ) . unwrap ( ) ;
835+ se. write_all ( entry_data) . unwrap ( ) ;
836+ producer. submit ( se) . unwrap ( )
837+ }
838+
839+ #[ test]
840+ fn test_reclaim_frees_ring_slots ( ) {
841+ let ring = make_ring ( 4 ) ;
842+ let ( mut producer, mut consumer, _) = make_test_producer ( & ring) ;
843+
844+ // Fill the ring with ReadOnly entries
845+ send_readonly ( & mut producer, b"a" ) ;
846+ send_readonly ( & mut producer, b"b" ) ;
847+ send_readonly ( & mut producer, b"c" ) ;
848+ send_readonly ( & mut producer, b"d" ) ;
849+
850+ // Ring is now full - next submit should fail with Backpressure
851+ let mut se = producer. chain ( ) . entry ( 1 ) . build ( ) . unwrap ( ) ;
852+ se. write_all ( b"e" ) . unwrap ( ) ;
853+ let res = producer. submit ( se) ;
854+ assert ! (
855+ matches!( res, Err ( VirtqError :: Backpressure ) ) ,
856+ "expected Backpressure from full ring"
857+ ) ;
858+
859+ // Consumer acks all entries
860+ while let Some ( ( _, completion) ) = consumer. poll ( 1024 ) . unwrap ( ) {
861+ consumer. complete ( completion) . unwrap ( ) ;
862+ }
863+
864+ // Reclaim should free ring slots without losing data
865+ let count = producer. reclaim ( ) . unwrap ( ) ;
866+ assert_eq ! ( count, 4 , "expected 4 reclaimed entries" ) ;
867+
868+ // Ring should have space now
869+ send_readonly ( & mut producer, b"e" ) ;
870+ }
871+
872+ #[ test]
873+ fn test_reclaim_buffers_rw_completions ( ) {
874+ let ring = make_ring ( 4 ) ;
875+ let ( mut producer, mut consumer, _) = make_test_producer ( & ring) ;
876+
877+ // Submit a ReadWrite entry
878+ let tok = send_readwrite ( & mut producer, b"request" , 64 ) ;
879+
880+ // Consumer processes and writes response
881+ let ( _, completion) = consumer. poll ( 1024 ) . unwrap ( ) . unwrap ( ) ;
882+ let SendCompletion :: Writable ( mut wc) = completion else {
883+ panic ! ( "expected writable" ) ;
884+ } ;
885+ wc. write_all ( b"response-data" ) . unwrap ( ) ;
886+ consumer. complete ( wc. into ( ) ) . unwrap ( ) ;
887+
888+ // Reclaim buffers the completion (doesn't discard it)
889+ let count = producer. reclaim ( ) . unwrap ( ) ;
890+ assert_eq ! ( count, 1 ) ;
891+
892+ // poll() should return the buffered completion
893+ let cqe = producer. poll ( ) . unwrap ( ) . unwrap ( ) ;
894+ assert_eq ! ( cqe. token, tok) ;
895+ assert_eq ! ( & cqe. data[ ..] , b"response-data" ) ;
896+ }
897+
898+ #[ test]
899+ fn test_reclaim_then_poll_preserves_order ( ) {
900+ let ring = make_ring ( 8 ) ;
901+ let ( mut producer, mut consumer, _) = make_test_producer ( & ring) ;
902+
903+ // Submit 3 entries: RO, RW, RO
904+ let tok_ro1 = send_readonly ( & mut producer, b"log1" ) ;
905+ let tok_rw = send_readwrite ( & mut producer, b"call" , 64 ) ;
906+ let tok_ro2 = send_readonly ( & mut producer, b"log2" ) ;
907+
908+ // Consumer processes all 3
909+ let ( _, c1) = consumer. poll ( 1024 ) . unwrap ( ) . unwrap ( ) ;
910+ consumer. complete ( c1) . unwrap ( ) ; // ack RO
911+
912+ let ( _, c2) = consumer. poll ( 1024 ) . unwrap ( ) . unwrap ( ) ;
913+ let SendCompletion :: Writable ( mut wc) = c2 else {
914+ panic ! ( "expected writable" ) ;
915+ } ;
916+ wc. write_all ( b"result" ) . unwrap ( ) ;
917+ consumer. complete ( wc. into ( ) ) . unwrap ( ) ; // complete RW
918+
919+ let ( _, c3) = consumer. poll ( 1024 ) . unwrap ( ) . unwrap ( ) ;
920+ consumer. complete ( c3) . unwrap ( ) ; // ack RO
921+
922+ // Reclaim all 3
923+ let count = producer. reclaim ( ) . unwrap ( ) ;
924+ assert_eq ! ( count, 3 ) ;
925+
926+ // poll() returns them in order
927+ let cqe1 = producer. poll ( ) . unwrap ( ) . unwrap ( ) ;
928+ assert_eq ! ( cqe1. token, tok_ro1) ;
929+ assert ! ( cqe1. data. is_empty( ) ) ;
930+
931+ let cqe2 = producer. poll ( ) . unwrap ( ) . unwrap ( ) ;
932+ assert_eq ! ( cqe2. token, tok_rw) ;
933+ assert_eq ! ( & cqe2. data[ ..] , b"result" ) ;
934+
935+ let cqe3 = producer. poll ( ) . unwrap ( ) . unwrap ( ) ;
936+ assert_eq ! ( cqe3. token, tok_ro2) ;
937+ assert ! ( cqe3. data. is_empty( ) ) ;
938+
939+ // No more
940+ assert ! ( producer. poll( ) . unwrap( ) . is_none( ) ) ;
941+ }
942+
943+ #[ test]
944+ fn test_reclaim_mixed_with_poll ( ) {
945+ let ring = make_ring ( 8 ) ;
946+ let ( mut producer, mut consumer, _) = make_test_producer ( & ring) ;
947+
948+ // Submit and complete 2 entries
949+ send_readonly ( & mut producer, b"x" ) ;
950+ let tok_rw = send_readwrite ( & mut producer, b"y" , 64 ) ;
951+
952+ let ( _, c1) = consumer. poll ( 1024 ) . unwrap ( ) . unwrap ( ) ;
953+ consumer. complete ( c1) . unwrap ( ) ;
954+
955+ let ( _, c2) = consumer. poll ( 1024 ) . unwrap ( ) . unwrap ( ) ;
956+ let SendCompletion :: Writable ( mut wc) = c2 else {
957+ panic ! ( "expected writable" ) ;
958+ } ;
959+ wc. write_all ( b"reply" ) . unwrap ( ) ;
960+ consumer. complete ( wc. into ( ) ) . unwrap ( ) ;
961+
962+ // poll() consumes first entry directly from ring
963+ let cqe1 = producer. poll ( ) . unwrap ( ) . unwrap ( ) ;
964+ assert ! ( cqe1. data. is_empty( ) ) ;
965+
966+ // reclaim() buffers second entry
967+ let count = producer. reclaim ( ) . unwrap ( ) ;
968+ assert_eq ! ( count, 1 ) ;
969+
970+ // poll() returns the buffered one
971+ let cqe2 = producer. poll ( ) . unwrap ( ) . unwrap ( ) ;
972+ assert_eq ! ( cqe2. token, tok_rw) ;
973+ assert_eq ! ( & cqe2. data[ ..] , b"reply" ) ;
974+ }
797975}
798976#[ cfg( all( test, loom) ) ]
799977mod fuzz {
0 commit comments