@@ -1440,3 +1440,185 @@ fn interrupt_infinite_loop_stress_test() {
14401440 handle. join ( ) . unwrap ( ) ;
14411441 }
14421442}
1443+
1444+ // Validates that kill delivery stays accurate when a sandbox hops between threads
1445+ // mid-call and shares a thread ID with another sandbox, ensuring only the intended
1446+ // VM is interrupted while bait sandboxes keep running.
1447+ #[ test]
1448+ fn interrupt_infinite_moving_loop_stress_test ( ) {
1449+ use std:: sync:: Arc ;
1450+ use std:: thread;
1451+
1452+ // We have a high thread count to stress test and to have interesting interleavings
1453+ const NUM_THREADS : usize = 1000 ;
1454+
1455+ let mut handles = vec ! [ ] ;
1456+
1457+ for _ in 0 ..NUM_THREADS {
1458+ handles. push ( thread:: spawn ( move || {
1459+ let entered_guest = Arc :: new ( AtomicBool :: new ( false ) ) ;
1460+ let entered_guest_clone = entered_guest. clone ( ) ;
1461+
1462+ let mut uninit = new_uninit_rust ( ) . unwrap ( ) ;
1463+ // Register a host function that waits on the barrier
1464+ uninit
1465+ . register ( "WaitForKill" , move || {
1466+ entered_guest. store ( true , Ordering :: Relaxed ) ;
1467+ Ok ( ( ) )
1468+ } )
1469+ . unwrap ( ) ;
1470+ let uninit2 = new_uninit_rust ( ) . unwrap ( ) ;
1471+
1472+ // These 2 sandboxes will have the same TID
1473+ let sandbox = uninit. evolve ( ) . unwrap ( ) ;
1474+ let bait = uninit2. evolve ( ) . unwrap ( ) ;
1475+
1476+ let interrupt = sandbox. interrupt_handle ( ) ;
1477+
1478+ let kill_handle = thread:: spawn ( move || {
1479+ let entered_before_kill = entered_guest_clone. load ( Ordering :: Relaxed ) ;
1480+ interrupt. kill ( ) ;
1481+ entered_before_kill
1482+ } ) ;
1483+
1484+ let mut sandbox_slot = Some ( sandbox) ;
1485+ let mut bait_slot = Some ( bait) ;
1486+
1487+ // bait-sandbox should NEVER be interrupted which is why we can unwrap()
1488+ // sandbox may or may not be interrupted depending on whether `kill()` was called prematurely or not
1489+ let sandbox_res = match rand:: random_range ( ..8u8 ) {
1490+ // sandbox on main thread, then bait on main thread
1491+ 0 => {
1492+ let res = sandbox_slot
1493+ . as_mut ( )
1494+ . unwrap ( )
1495+ . call :: < String > ( "Echo" , "Real" . to_string ( ) ) ;
1496+ bait_slot
1497+ . as_mut ( )
1498+ . unwrap ( )
1499+ . call :: < String > ( "Echo" , "Bait" . to_string ( ) )
1500+ . expect ( "Bait call should never be interrupted" ) ;
1501+ res
1502+ }
1503+ // bait on main thread, then sandbox on main thread
1504+ 1 => {
1505+ bait_slot
1506+ . as_mut ( )
1507+ . unwrap ( )
1508+ . call :: < String > ( "Echo" , "Bait" . to_string ( ) )
1509+ . expect ( "Bait call should never be interrupted" ) ;
1510+ sandbox_slot
1511+ . as_mut ( )
1512+ . unwrap ( )
1513+ . call :: < String > ( "Echo" , "Real" . to_string ( ) )
1514+ }
1515+ // sandbox on spawned thread, bait on main thread
1516+ 2 => {
1517+ let mut sandbox = sandbox_slot. take ( ) . unwrap ( ) ;
1518+ let sandbox_handle =
1519+ thread:: spawn ( move || sandbox. call :: < String > ( "Echo" , "Real" . to_string ( ) ) ) ;
1520+ bait_slot
1521+ . as_mut ( )
1522+ . unwrap ( )
1523+ . call :: < String > ( "Echo" , "Bait" . to_string ( ) )
1524+ . expect ( "Bait call should never be interrupted" ) ;
1525+ sandbox_handle. join ( ) . unwrap ( )
1526+ }
1527+ // bait on spawned thread, sandbox on main thread
1528+ 3 => {
1529+ let mut bait = bait_slot. take ( ) . unwrap ( ) ;
1530+ let bait_handle = thread:: spawn ( move || {
1531+ bait. call :: < String > ( "Echo" , "Bait" . to_string ( ) )
1532+ . expect ( "Bait call should never be interrupted" ) ;
1533+ } ) ;
1534+ let res = sandbox_slot
1535+ . as_mut ( )
1536+ . unwrap ( )
1537+ . call :: < String > ( "Echo" , "Real" . to_string ( ) ) ;
1538+ bait_handle. join ( ) . unwrap ( ) ;
1539+ res
1540+ }
1541+ // sandbox on main thread, bait on spawned thread
1542+ 4 => {
1543+ let res = sandbox_slot
1544+ . as_mut ( )
1545+ . unwrap ( )
1546+ . call :: < String > ( "Echo" , "Real" . to_string ( ) ) ;
1547+ let mut bait = bait_slot. take ( ) . unwrap ( ) ;
1548+ let bait_handle = thread:: spawn ( move || {
1549+ bait. call :: < String > ( "Echo" , "Bait" . to_string ( ) )
1550+ . expect ( "Bait call should never be interrupted" ) ;
1551+ } ) ;
1552+ bait_handle. join ( ) . unwrap ( ) ;
1553+ res
1554+ }
1555+ // bait on main thread, sandbox on spawned thread
1556+ 5 => {
1557+ bait_slot
1558+ . as_mut ( )
1559+ . unwrap ( )
1560+ . call :: < String > ( "Echo" , "Bait" . to_string ( ) )
1561+ . expect ( "Bait call should never be interrupted" ) ;
1562+ let mut sandbox = sandbox_slot. take ( ) . unwrap ( ) ;
1563+ let sandbox_handle =
1564+ thread:: spawn ( move || sandbox. call :: < String > ( "Echo" , "Real" . to_string ( ) ) ) ;
1565+ sandbox_handle. join ( ) . unwrap ( )
1566+ }
1567+ // sandbox on spawned thread, bait on spawned thread
1568+ 6 => {
1569+ let mut sandbox = sandbox_slot. take ( ) . unwrap ( ) ;
1570+ let sandbox_handle =
1571+ thread:: spawn ( move || sandbox. call :: < String > ( "Echo" , "Real" . to_string ( ) ) ) ;
1572+ let mut bait = bait_slot. take ( ) . unwrap ( ) ;
1573+ let bait_handle = thread:: spawn ( move || {
1574+ bait. call :: < String > ( "Echo" , "Bait" . to_string ( ) )
1575+ . expect ( "Bait call should never be interrupted" ) ;
1576+ } ) ;
1577+ bait_handle. join ( ) . unwrap ( ) ;
1578+ sandbox_handle. join ( ) . unwrap ( )
1579+ }
1580+ // bait on spawned thread, sandbox on spawned thread (spawn bait first)
1581+ 7 => {
1582+ let mut bait = bait_slot. take ( ) . unwrap ( ) ;
1583+ let bait_handle = thread:: spawn ( move || {
1584+ bait. call :: < String > ( "Echo" , "Bait" . to_string ( ) )
1585+ . expect ( "Bait call should never be interrupted" ) ;
1586+ } ) ;
1587+ let mut sandbox = sandbox_slot. take ( ) . unwrap ( ) ;
1588+ let sandbox_handle =
1589+ thread:: spawn ( move || sandbox. call :: < String > ( "Echo" , "Real" . to_string ( ) ) ) ;
1590+ bait_handle. join ( ) . unwrap ( ) ;
1591+ sandbox_handle. join ( ) . unwrap ( )
1592+ }
1593+ _ => unreachable ! ( ) ,
1594+ } ;
1595+
1596+ let entered_before_kill = kill_handle. join ( ) . unwrap ( ) ;
1597+
1598+ // If the guest entered before calling kill, then we know for a fact the call should have been canceled since kill() was NOT premature.
1599+ if entered_before_kill {
1600+ assert ! ( matches!(
1601+ sandbox_res,
1602+ Err ( HyperlightError :: ExecutionCanceledByHost ( ) )
1603+ ) ) ;
1604+ }
1605+
1606+ // If we did NOT enter the guest before calling kill, then the call may or may not have been canceled depending on timing.
1607+ match sandbox_res {
1608+ Err ( HyperlightError :: ExecutionCanceledByHost ( ) ) => {
1609+ // OK!
1610+ }
1611+ Ok ( _) => {
1612+ // OK!
1613+ }
1614+ Err ( e) => {
1615+ panic ! ( "Got unexpected error: {:?}" , e) ;
1616+ }
1617+ }
1618+ } ) ) ;
1619+ }
1620+
1621+ for handle in handles {
1622+ handle. join ( ) . unwrap ( ) ;
1623+ }
1624+ }
0 commit comments