Skip to content

Commit ee98dd8

Browse files
committed
Add tests that tests moving sandbox across thread doesn't cancel wrong sandbox
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent 2494339 commit ee98dd8

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

src/hyperlight_host/tests/integration_test.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)