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