@@ -403,9 +403,23 @@ where
403403 }
404404
405405 fn kick ( & mut self ) {
406- if self . is_activated ( ) {
407- self . pending_event_ack = true ;
408-
406+ // `kick()` runs on EVERY vm resume (`Vmm::resume_vm` -> `kick_virtio_devices`),
407+ // which covers three distinct paths:
408+ // - snapshot-restore resume: the source's `prepare_save` queued a
409+ // `TRANSPORT_RESET_EVENT` into the (snapshotted) used ring, and `restore()`
410+ // re-armed `pending_event_ack` from the saved activation flag;
411+ // - post-snapshot source resume (diff checkpoints): `prepare_save` queued the
412+ // reset and armed the gate in this very process;
413+ // - PLAIN pause->resume (no snapshot anywhere): nothing was queued and nothing
414+ // must be signaled.
415+ // Arming the gate *here* used to break the third path: with no reset queued
416+ // there is nothing for the guest to ack, so the gate never cleared — all
417+ // host->guest RX black-holed and the event loop busy-spun on the undeliverable
418+ // backend backlog (the ADR 0074 rung-2 parked-paused un-pause wedge). So:
419+ // signal only when a reset is genuinely awaiting the guest's ack, and never arm
420+ // from here — `prepare_save` (in-process) and `restore()` (cross-snapshot) own
421+ // the arming.
422+ if self . is_activated ( ) && self . pending_event_ack {
409423 // Vsock has a complicated protocol that isn't resilient to any packet loss,
410424 // so for Vsock we don't support connection persistence through snapshot. Any
411425 // in-flight packets or events are simply lost and Vsock is restored 'empty'.
@@ -618,39 +632,71 @@ mod tests {
618632 }
619633
620634 #[ test]
621- fn test_kick_when_active_arms_pending_event_ack ( ) {
622- // Restore path: kick() is invoked after the snapshot is loaded to re-deliver the
623- // TRANSPORT_RESET interrupt. It must arm the RX gate so the post-restore RX/EVQ
624- // race cannot deliver data ahead of the guest ack.
635+ fn test_kick_when_armed_keeps_gate_and_suppresses_rx ( ) {
636+ // Restore / post-snapshot-source resume: a TRANSPORT_RESET is awaiting the
637+ // guest's ack (`pending_event_ack` was armed by prepare_save or restore()).
638+ // kick() must re-signal while KEEPING the gate armed, so the post-restore
639+ // RX/EVQ race cannot deliver data ahead of the guest ack.
625640 let test_ctx = TestContext :: new ( ) ;
626641 let mut ctx = test_ctx. create_event_handler_context ( ) ;
627642 ctx. mock_activate ( test_ctx. mem . clone ( ) , test_ctx. interrupt . clone ( ) ) ;
628643
629- ctx. device . pending_event_ack = false ;
644+ ctx. device . pending_event_ack = true ;
630645 ctx. device . kick ( ) ;
631646
632647 assert ! (
633648 ctx. device. pending_event_ack,
634- "kick() on an active device must arm the RX gate "
649+ "kick() must keep the RX gate armed while the reset awaits its ack "
635650 ) ;
636651
637- // After kick(), the gate must actually suppress RX delivery.
652+ // With the gate armed, RX delivery must stay suppressed .
638653 ctx. device . backend . set_pending_rx ( true ) ;
639654 let progressed = ctx. device . process_rx ( ) . unwrap ( ) ;
640655 assert ! ( !progressed) ;
641656 assert_eq ! ( ctx. guest_rxvq. used. idx. get( ) , 0 ) ;
642657 }
643658
659+ #[ test]
660+ fn test_kick_plain_resume_unarmed_is_a_noop ( ) {
661+ // PLAIN pause->resume (no snapshot anywhere): no TRANSPORT_RESET was queued, so
662+ // kick() must neither arm the gate nor signal any queue. Arming here is the
663+ // ADR 0074 rung-2 wedge: nothing for the guest to ack -> the gate never clears
664+ // -> all host->guest RX black-holes and the event loop busy-spins.
665+ let test_ctx = TestContext :: new ( ) ;
666+ let mut ctx = test_ctx. create_event_handler_context ( ) ;
667+ ctx. mock_activate ( test_ctx. mem . clone ( ) , test_ctx. interrupt . clone ( ) ) ;
668+
669+ assert ! ( !ctx. device. pending_event_ack) ;
670+ ctx. device . kick ( ) ;
671+
672+ assert ! (
673+ !ctx. device. pending_event_ack,
674+ "kick() must never arm the RX gate itself"
675+ ) ;
676+ // No queue eventfd may be signaled on an unarmed kick (non-blocking read
677+ // errors when the eventfd has no pending count).
678+ ctx. device . queue_events [ TXQ_INDEX ] . read ( ) . unwrap_err ( ) ;
679+ ctx. device . queue_events [ RXQ_INDEX ] . read ( ) . unwrap_err ( ) ;
680+ ctx. device . queue_events [ EVQ_INDEX ] . read ( ) . unwrap_err ( ) ;
681+
682+ // And RX must still flow: the un-paused guest's connections are intact.
683+ ctx. device . backend . set_pending_rx ( true ) ;
684+ let progressed = ctx. device . process_rx ( ) . unwrap ( ) ;
685+ assert ! ( progressed, "RX must not be gated after a plain resume" ) ;
686+ }
687+
644688 #[ test]
645689 fn test_kick_replays_tx_notification_only ( ) {
646- // On restore, kick() must replay only the TX data queue (to re-process in-flight
647- // TX and re-arm avail_event). RX is gated by pending_event_ack so it needs no
648- // replay, and the event queue's data eventfd must not be notified -- that is the
649- // guest's TRANSPORT_RESET ack path; the event queue is signaled host->guest.
690+ // On restore (gate armed), kick() must replay only the TX data queue (to
691+ // re-process in-flight TX and re-arm avail_event). RX is gated by
692+ // pending_event_ack so it needs no replay, and the event queue's data eventfd
693+ // must not be notified -- that is the guest's TRANSPORT_RESET ack path; the
694+ // event queue is signaled host->guest.
650695 let test_ctx = TestContext :: new ( ) ;
651696 let mut ctx = test_ctx. create_event_handler_context ( ) ;
652697 ctx. mock_activate ( test_ctx. mem . clone ( ) , test_ctx. interrupt . clone ( ) ) ;
653698
699+ ctx. device . pending_event_ack = true ;
654700 ctx. device . kick ( ) ;
655701
656702 // TX queue eventfd was replayed for re-processing.
0 commit comments