#![allow(clippy::arc_with_non_send_sync)] use std::sync::{Arc, Condvar, Mutex}; use std::time::Duration; use crate::vm::Vm; use crate::{vm_debug, vm_trace, vm_warn}; use oxide_kernel::kernel::KernelCore; struct VmPoolInner { available: Vec, total_count: usize, } pub struct VmPool { kernel_core: Arc, inner: Mutex, condvar: Condvar, max_size: Option, } pub struct VmGuard { vm: Option, pool: Arc, dirty: bool, } impl VmPool { pub fn new(kernel_core: Arc, _min_size: usize, max_size: Option) -> Arc { Arc::new(Self { kernel_core, inner: Mutex::new(VmPoolInner { available: Vec::new(), total_count: 0, }), condvar: Condvar::new(), max_size, }) } fn new_vm(core: &Arc) -> Vm { Vm::with_kernel_core(Arc::clone(core)) } fn replace_vm(&self) -> Vm { Self::new_vm(&self.kernel_core) } pub fn spawn(self: &Arc) -> VmGuard { loop { let mut inner = self.inner.lock().unwrap(); if let Some(vm) = inner.available.pop() { vm_trace!("pool: reused vm, {} available", inner.available.len()); return VmGuard { vm: Some(vm), pool: Arc::clone(self), dirty: false, }; } let can_grow = match self.max_size { Some(max) => inner.total_count < max, None => true, }; if can_grow { inner.total_count += 1; vm_debug!("pool: growing to {} vms", inner.total_count); drop(inner); let vm = Self::new_vm(&self.kernel_core); return VmGuard { vm: Some(vm), pool: Arc::clone(self), dirty: false, }; } let (guard, wait) = self.condvar.wait_timeout(inner, Duration::from_secs(5)).unwrap(); inner = guard; if wait.timed_out() { vm_warn!("pool: wait timeout, force-growing to {} vms", inner.total_count + 1); inner.total_count += 1; drop(inner); let vm = Self::new_vm(&self.kernel_core); return VmGuard { vm: Some(vm), pool: Arc::clone(self), dirty: false, }; } } } } impl VmGuard { pub fn vm(&self) -> &Vm { self.vm.as_ref().expect("VmGuard has no VM") } pub fn vm_mut(&mut self) -> &mut Vm { self.vm.as_mut().expect("VmGuard has no VM") } } impl Drop for VmGuard { fn drop(&mut self) { let Some(mut vm) = self.vm.take() else { return; }; let mut inner = self.pool.inner.lock().unwrap(); if self.dirty { vm_debug!("pool: discarding dirty vm"); let new_vm = self.pool.replace_vm(); inner.available.push(new_vm); } else { vm.full_reset(); inner.available.push(vm); vm_trace!("pool: recycled clean vm, {} available", inner.available.len()); } self.pool.condvar.notify_one(); } } #[cfg(test)] mod tests { use super::*; use oxide_kernel::kernel::KernelConfig; fn test_kernel() -> Arc { KernelCore::new(KernelConfig::minimal()) } #[test] fn test_pool_spawn_returns_guard() { let kernel = test_kernel(); let pool = VmPool::new(kernel, 1, None); let guard = pool.spawn(); drop(guard); } #[test] fn test_pool_recycle_on_drop() { let kernel = test_kernel(); let pool = VmPool::new(kernel, 1, None); let guard = pool.spawn(); drop(guard); let guard2 = pool.spawn(); drop(guard2); } #[test] fn test_pool_grows_if_empty() { let kernel = test_kernel(); let pool = VmPool::new(Arc::clone(&kernel), 1, Some(3)); let g1 = pool.spawn(); let g2 = pool.spawn(); drop(g1); drop(g2); } }