Skip to content

Commit 23b3293

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 23b3293

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

src/hyperlight_host/tests/integration_test.rs

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

Comments
 (0)