Skip to content

Commit eb4ec1d

Browse files
committed
feat(virtq): harden virtq snapshoting
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
1 parent 60c0265 commit eb4ec1d

11 files changed

Lines changed: 1021 additions & 128 deletions

File tree

src/hyperlight_common/src/virtq/consumer.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ limitations under the License.
1515
*/
1616

1717
use alloc::vec;
18-
use alloc::vec::Vec;
1918

2019
use bytes::Bytes;
2120
use fixedbitset::FixedBitSet;

src/hyperlight_common/src/virtq/pool.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,10 +601,61 @@ impl RecyclePool {
601601
})
602602
}
603603

604+
/// Rebuild pool state so that every address in `allocated` is removed
605+
/// from the free list, matching externally known inflight state.
606+
pub fn restore_allocated(&self, allocated: &[u64]) -> Result<(), AllocError> {
607+
self.reset();
608+
609+
if allocated.is_empty() {
610+
return Ok(());
611+
}
612+
613+
let mut inner = self.inner.borrow_mut();
614+
615+
for &addr in allocated {
616+
let pos = inner
617+
.free
618+
.iter()
619+
.position(|&a| a == addr)
620+
.ok_or(AllocError::InvalidFree(addr, inner.slot_size))?;
621+
622+
inner.free.swap_remove(pos);
623+
}
624+
625+
Ok(())
626+
}
627+
628+
/// Compute the address of slot `index`.
629+
///
630+
/// Returns `None` if `index >= count`.
631+
pub fn slot_addr(&self, index: usize) -> Option<u64> {
632+
let inner = self.inner.borrow();
633+
if index < inner.count {
634+
Some(inner.base_addr + (index * inner.slot_size) as u64)
635+
} else {
636+
None
637+
}
638+
}
639+
604640
/// Number of free slots.
605641
pub fn num_free(&self) -> usize {
606642
self.inner.borrow().free.len()
607643
}
644+
645+
/// Base address of the pool region.
646+
pub fn base_addr(&self) -> u64 {
647+
self.inner.borrow().base_addr
648+
}
649+
650+
/// Slot size in bytes.
651+
pub fn slot_size(&self) -> usize {
652+
self.inner.borrow().slot_size
653+
}
654+
655+
/// Number of slots in the pool.
656+
pub fn count(&self) -> usize {
657+
self.inner.borrow().count
658+
}
608659
}
609660

610661
impl BufferProvider for RecyclePool {
@@ -664,6 +715,11 @@ mod tests {
664715
BufferPool::<L, U>::new(base, size).unwrap()
665716
}
666717

718+
fn make_recycle_pool(slot_count: usize, slot_size: usize) -> RecyclePool {
719+
let base = 0x80000u64;
720+
RecyclePool::new(base, slot_count * slot_size, slot_size).unwrap()
721+
}
722+
667723
#[test]
668724
fn test_slab_new_success() {
669725
let slab = Slab::<256>::new(0x10000, 1024).unwrap();
@@ -1223,6 +1279,77 @@ mod tests {
12231279
let a = pool.inner.borrow_mut().alloc(256).unwrap();
12241280
assert!(a.len > 0);
12251281
}
1282+
1283+
#[test]
1284+
fn test_recycle_pool_restore_allocated_removes_from_free_list() {
1285+
let pool = make_recycle_pool(4, 4096);
1286+
assert_eq!(pool.num_free(), 4);
1287+
1288+
let addrs = [0x80000, 0x81000]; // slots 0 and 1
1289+
pool.restore_allocated(&addrs).unwrap();
1290+
assert_eq!(pool.num_free(), 2);
1291+
1292+
// Allocating should only return the two remaining slots
1293+
let a1 = pool.alloc(4096).unwrap();
1294+
let a2 = pool.alloc(4096).unwrap();
1295+
assert!(pool.alloc(4096).is_err());
1296+
1297+
// The allocated addresses should be the non-restored ones
1298+
let mut got = [a1.addr, a2.addr];
1299+
got.sort();
1300+
assert_eq!(got, [0x82000, 0x83000]);
1301+
}
1302+
1303+
#[test]
1304+
fn test_recycle_pool_restore_allocated_invalid_addr_returns_error() {
1305+
let pool = make_recycle_pool(4, 4096);
1306+
let result = pool.restore_allocated(&[0xDEAD]);
1307+
assert!(result.is_err());
1308+
}
1309+
1310+
#[test]
1311+
fn test_recycle_pool_restore_allocated_then_dealloc_roundtrip() {
1312+
let pool = make_recycle_pool(4, 4096);
1313+
let addr = 0x81000u64;
1314+
1315+
pool.restore_allocated(&[addr]).unwrap();
1316+
assert_eq!(pool.num_free(), 3);
1317+
1318+
// Dealloc the restored address
1319+
pool.dealloc(Allocation { addr, len: 4096 }).unwrap();
1320+
assert_eq!(pool.num_free(), 4);
1321+
}
1322+
1323+
#[test]
1324+
fn test_recycle_pool_restore_allocated_all_slots() {
1325+
let pool = make_recycle_pool(4, 4096);
1326+
let addrs: Vec<u64> = (0..4).map(|i| 0x80000 + i * 4096).collect();
1327+
1328+
pool.restore_allocated(&addrs).unwrap();
1329+
assert_eq!(pool.num_free(), 0);
1330+
assert!(pool.alloc(4096).is_err());
1331+
}
1332+
1333+
#[test]
1334+
fn test_recycle_pool_restore_allocated_empty_list_is_noop() {
1335+
let pool = make_recycle_pool(4, 4096);
1336+
pool.restore_allocated(&[]).unwrap();
1337+
assert_eq!(pool.num_free(), 4);
1338+
}
1339+
1340+
#[test]
1341+
fn test_recycle_pool_restore_allocated_resets_first() {
1342+
let pool = make_recycle_pool(4, 4096);
1343+
1344+
// Allocate some slots
1345+
let _ = pool.alloc(4096).unwrap();
1346+
let _ = pool.alloc(4096).unwrap();
1347+
assert_eq!(pool.num_free(), 2);
1348+
1349+
// restore_allocated resets then removes - so 4 - 1 = 3
1350+
pool.restore_allocated(&[0x80000]).unwrap();
1351+
assert_eq!(pool.num_free(), 3);
1352+
}
12261353
}
12271354

12281355
#[cfg(test)]

0 commit comments

Comments
 (0)