Skip to main content

mimobox_vm/
pool.rs

1//! microVM prewarm pool.
2//!
3//! Provides thread-safe `KvmBackend` prewarming and reuse so commands do not need to
4//! recreate and boot a VM for every execution.
5
6use std::collections::VecDeque;
7use std::sync::{Arc, Mutex, MutexGuard};
8use std::time::{Duration, Instant};
9
10use thiserror::Error;
11
12use crate::vm::LifecycleError;
13use crate::{
14    GuestCommandResult, GuestExecOptions, HttpRequest, HttpResponse, MicrovmConfig, MicrovmError,
15    StreamEvent,
16};
17use mimobox_core::{DirEntry, FileStat, SandboxConfig, SandboxSnapshot};
18
19#[cfg(all(target_os = "linux", feature = "kvm"))]
20use crate::{KvmBackend, KvmExitReason};
21
22/// Configuration for a fully booted microVM prewarm pool.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct VmPoolConfig {
25    /// Minimum number of idle VMs to prewarm and keep available.
26    pub min_size: usize,
27    /// Maximum number of idle VMs retained by the pool.
28    pub max_size: usize,
29    /// Maximum duration an idle VM may be retained before eviction.
30    pub max_idle_duration: Duration,
31    /// Release interval used for health checks; `None` disables release-time checks.
32    pub health_check_interval: Option<u32>,
33}
34
35impl Default for VmPoolConfig {
36    fn default() -> Self {
37        Self {
38            min_size: 1,
39            max_size: 16,
40            max_idle_duration: Duration::from_secs(30),
41            health_check_interval: None,
42        }
43    }
44}
45
46/// Runtime statistics for a [`VmPool`].
47#[derive(Debug, Clone, Default, PartialEq, Eq)]
48pub struct VmPoolStats {
49    /// Number of acquisitions satisfied by an already idle VM.
50    pub hit_count: u64,
51    /// Number of acquisitions that had to create a new VM.
52    pub miss_count: u64,
53    /// Number of VMs evicted because of timeout, failed health checks, or capacity pressure.
54    pub evict_count: u64,
55    /// Current number of idle VMs retained by the pool.
56    pub idle_count: usize,
57    /// Current number of VM handles checked out from the pool.
58    pub in_use_count: usize,
59}
60
61/// Error returned by [`VmPool`] operations.
62#[derive(Debug, Error)]
63pub enum PoolError {
64    /// Pool capacity configuration is invalid.
65    #[error("invalid pool config: min_size={min_size}, max_size={max_size}")]
66    InvalidConfig {
67        /// Invalid minimum idle target.
68        min_size: usize,
69        /// Invalid maximum capacity.
70        max_size: usize,
71    },
72
73    /// Internal shared state lock is poisoned.
74    #[error("warm pool state lock poisoned")]
75    StatePoisoned,
76
77    /// Underlying microVM error.
78    #[error(transparent)]
79    Microvm(
80        /// Source microVM error.
81        #[from]
82        MicrovmError,
83    ),
84}
85
86struct IdleVm {
87    backend: Backend,
88    last_used: Instant,
89}
90
91impl IdleVm {
92    fn new(backend: Backend) -> Self {
93        Self {
94            backend,
95            last_used: Instant::now(),
96        }
97    }
98}
99
100#[derive(Default)]
101struct PoolState {
102    idle: VecDeque<IdleVm>,
103    in_use_count: usize,
104    hit_count: u64,
105    miss_count: u64,
106    evict_count: u64,
107    recycle_count: u64,
108}
109
110impl PoolState {
111    fn snapshot(&self) -> VmPoolStats {
112        VmPoolStats {
113            hit_count: self.hit_count,
114            miss_count: self.miss_count,
115            evict_count: self.evict_count,
116            idle_count: self.idle.len(),
117            in_use_count: self.in_use_count,
118        }
119    }
120
121    fn should_health_check_on_recycle(&mut self, health_check_interval: Option<u32>) -> bool {
122        self.recycle_count = self.recycle_count.saturating_add(1);
123        match health_check_interval {
124            Some(interval) if interval > 0 => {
125                self.recycle_count.is_multiple_of(u64::from(interval))
126            }
127            _ => false,
128        }
129    }
130}
131
132struct VmPoolInner {
133    base_config: SandboxConfig,
134    config: MicrovmConfig,
135    pool_config: VmPoolConfig,
136    health_check_command: Vec<String>,
137    state: Mutex<PoolState>,
138}
139
140impl Drop for VmPoolInner {
141    fn drop(&mut self) {
142        // 获取 idle 队列中所有 VM 并逐个 shutdown,释放 KVM fd。
143        let idle = match self.state.lock() {
144            Ok(mut state) => std::mem::take(&mut state.idle),
145            Err(_) => {
146                tracing::warn!("VmPool drop 时状态锁已中毒,无法清理 idle VM");
147                return;
148            }
149        };
150        let count = idle.len();
151        for entry in idle {
152            destroy_idle_entry(entry, "VmPool drop 清理");
153        }
154        if count > 0 {
155            tracing::debug!(count, "VmPool drop 清理完成");
156        }
157    }
158}
159
160impl VmPoolInner {
161    fn lock_state(&self) -> Result<MutexGuard<'_, PoolState>, PoolError> {
162        self.state.lock().map_err(|_| PoolError::StatePoisoned)
163    }
164
165    fn rollback_in_use(&self) {
166        match self.state.lock() {
167            Ok(mut state) => {
168                state.in_use_count = state.in_use_count.saturating_sub(1);
169            }
170            Err(_) => {
171                tracing::warn!("回滚 in_use 计数失败:VM 预热池状态锁已中毒");
172            }
173        }
174    }
175
176    fn take_expired_idle(&self) -> Result<Vec<IdleVm>, PoolError> {
177        let mut state = self.lock_state()?;
178        let now = Instant::now();
179        let mut expired = Vec::new();
180
181        loop {
182            let should_evict = match state.idle.front() {
183                Some(entry) => {
184                    now.saturating_duration_since(entry.last_used)
185                        >= self.pool_config.max_idle_duration
186                }
187                None => false,
188            };
189
190            if !should_evict {
191                break;
192            }
193
194            if let Some(entry) = state.idle.pop_front() {
195                state.evict_count += 1;
196                expired.push(entry);
197            } else {
198                break;
199            }
200        }
201
202        Ok(expired)
203    }
204
205    fn push_idle_after_release(&self, backend: Backend) -> Option<IdleVm> {
206        match self.state.lock() {
207            Ok(mut state) => {
208                let evicted = if state.idle.len() >= self.pool_config.max_size {
209                    let entry = state.idle.pop_front();
210                    if entry.is_some() {
211                        state.evict_count += 1;
212                    }
213                    entry
214                } else {
215                    None
216                };
217
218                state.idle.push_back(IdleVm::new(backend));
219                evicted
220            }
221            Err(_) => {
222                tracing::warn!("回收 VM 失败:无法重新放回 idle 队列,直接销毁 VM");
223                destroy_backend(backend, "状态锁已中毒");
224                None
225            }
226        }
227    }
228
229    fn replenish_if_needed(&self) {
230        let should_replenish = match self.state.lock() {
231            Ok(state) => state.idle.len() < self.pool_config.min_size,
232            Err(_) => {
233                tracing::warn!("检查是否需要补充 VM 失败:状态锁已中毒");
234                return;
235            }
236        };
237
238        if !should_replenish {
239            return;
240        }
241
242        match create_backend(&self.base_config, &self.config) {
243            Ok(backend) => {
244                let evicted = self.push_idle_after_release(backend);
245                if let Some(entry) = evicted {
246                    destroy_idle_entry(entry, "补充 VM 时触发容量淘汰");
247                }
248            }
249            Err(err) => {
250                tracing::warn!("补充预热 VM 失败: {err}");
251            }
252        }
253    }
254
255    fn recycle(&self, mut backend: Backend) {
256        let should_health_check = match self.state.lock() {
257            Ok(mut state) => {
258                state.in_use_count = state.in_use_count.saturating_sub(1);
259                state.should_health_check_on_recycle(self.pool_config.health_check_interval)
260            }
261            Err(_) => {
262                tracing::warn!("回收 VM 失败:状态锁已中毒,直接销毁 VM");
263                destroy_backend(backend, "状态锁已中毒");
264                return;
265            }
266        };
267
268        if !backend_is_reusable(&backend) {
269            self.mark_evict();
270            destroy_backend(backend, "VM 已异常,无法复用");
271            self.replenish_if_needed();
272            return;
273        }
274
275        if should_health_check {
276            match health_check_backend(&mut backend, &self.health_check_command) {
277                Ok(true) => {}
278                Ok(false) => {
279                    self.mark_evict();
280                    destroy_backend(backend, "健康检查失败");
281                    self.replenish_if_needed();
282                    return;
283                }
284                Err(err) => {
285                    tracing::warn!("VM 健康检查失败,回收时直接驱逐: {err}");
286                    self.mark_evict();
287                    destroy_backend(backend, "健康检查异常");
288                    self.replenish_if_needed();
289                    return;
290                }
291            }
292        }
293
294        clear_backend_artifacts(&mut backend);
295        let evicted = self.push_idle_after_release(backend);
296        if let Some(entry) = evicted {
297            destroy_idle_entry(entry, "LRU 容量淘汰");
298        }
299    }
300
301    fn mark_evict(&self) {
302        match self.state.lock() {
303            Ok(mut state) => {
304                state.evict_count += 1;
305            }
306            Err(_) => {
307                tracing::warn!("记录驱逐计数失败:VM 预热池状态锁已中毒");
308            }
309        }
310    }
311}
312
313/// Thread-safe pool of fully booted microVMs.
314///
315/// `VmPool` amortizes VM creation and boot cost by keeping ready guests in an idle
316/// queue. A borrowed [`PooledVm`] returns to the pool automatically when dropped if
317/// the backend is still reusable.
318#[derive(Clone)]
319pub struct VmPool {
320    inner: Arc<VmPoolInner>,
321}
322
323impl VmPool {
324    /// Creates a microVM prewarm pool with the default [`SandboxConfig`].
325    pub fn new(config: MicrovmConfig, pool_config: VmPoolConfig) -> Result<Self, PoolError> {
326        Self::new_with_base(SandboxConfig::default(), config, pool_config)
327    }
328
329    /// Creates a microVM prewarm pool with an explicit base sandbox configuration.
330    ///
331    /// The pool validates capacity limits, platform support, and microVM assets
332    /// before warming the configured minimum number of guests.
333    pub fn new_with_base(
334        base_config: SandboxConfig,
335        config: MicrovmConfig,
336        pool_config: VmPoolConfig,
337    ) -> Result<Self, PoolError> {
338        if pool_config.max_size == 0 || pool_config.min_size > pool_config.max_size {
339            return Err(PoolError::InvalidConfig {
340                min_size: pool_config.min_size,
341                max_size: pool_config.max_size,
342            });
343        }
344
345        ensure_pool_supported()?;
346        config.validate()?;
347
348        let pool = Self {
349            inner: Arc::new(VmPoolInner {
350                base_config,
351                config,
352                pool_config,
353                health_check_command: vec!["/bin/true".to_string()],
354                state: Mutex::new(PoolState::default()),
355            }),
356        };
357
358        if pool_config.min_size > 0 {
359            pool.warm(pool_config.min_size)?;
360        }
361
362        Ok(pool)
363    }
364
365    /// Acquires an executable microVM instance from the pool.
366    ///
367    /// Expired idle VMs are evicted first. If no reusable idle VM is available, a new
368    /// backend is created and counted as a miss.
369    pub fn acquire(&self) -> Result<PooledVm, PoolError> {
370        let _span = tracing::info_span!("pool_acquire").entered();
371        #[cfg(feature = "boot-profile")]
372        let acquire_started_at = Instant::now();
373        #[cfg(feature = "boot-profile")]
374        let expired_cleanup_started_at = Instant::now();
375        let expired = self.inner.take_expired_idle()?;
376        #[cfg(feature = "boot-profile")]
377        let expired_idle_cleanup = expired_cleanup_started_at.elapsed();
378        for entry in expired {
379            destroy_idle_entry(entry, "空闲超时");
380        }
381
382        #[cfg(feature = "boot-profile")]
383        let state_checkout_started_at = Instant::now();
384        let reused = {
385            let mut state = self.inner.lock_state()?;
386            if let Some(entry) = state.idle.pop_back() {
387                state.hit_count += 1;
388                state.in_use_count += 1;
389                Some(entry.backend)
390            } else {
391                state.miss_count += 1;
392                state.in_use_count += 1;
393                None
394            }
395        };
396        #[cfg(feature = "boot-profile")]
397        let state_checkout = state_checkout_started_at.elapsed();
398
399        #[cfg(feature = "boot-profile")]
400        let backend_prepare_started_at = Instant::now();
401        #[cfg(feature = "boot-profile")]
402        let reused_hit = reused.is_some();
403        #[allow(clippy::question_mark)]
404        let backend = match reused {
405            Some(backend) => backend,
406            None => match create_backend(&self.inner.base_config, &self.inner.config) {
407                Ok(backend) => backend,
408                Err(err) => {
409                    self.inner.rollback_in_use();
410                    return Err(err.into());
411                }
412            },
413        };
414        #[cfg(feature = "boot-profile")]
415        let backend_prepare = backend_prepare_started_at.elapsed();
416
417        #[cfg(feature = "boot-profile")]
418        tracing::info!(
419            expired_idle_cleanup = ?expired_idle_cleanup,
420            state_checkout = ?state_checkout,
421            backend_prepare = ?backend_prepare,
422            reused = reused_hit,
423            total = ?acquire_started_at.elapsed(),
424            "[pool.acquire] 性能概览"
425        );
426
427        Ok(PooledVm {
428            backend: Some(backend),
429            pool: Arc::clone(&self.inner),
430        })
431    }
432
433    /// Warms the idle pool to at least `count` VMs.
434    ///
435    /// The effective target is capped by [`VmPoolConfig::max_size`]. The return value
436    /// is the number of new VMs inserted into the idle queue.
437    pub fn warm(&self, count: usize) -> Result<usize, PoolError> {
438        let expired = self.inner.take_expired_idle()?;
439        for entry in expired {
440            destroy_idle_entry(entry, "空闲超时");
441        }
442
443        let target_idle_size = count.min(self.inner.pool_config.max_size);
444        let current_idle = self.inner.lock_state()?.idle.len();
445        if current_idle >= target_idle_size {
446            return Ok(0);
447        }
448
449        let create_count = target_idle_size.saturating_sub(current_idle);
450        let mut created = Vec::with_capacity(create_count);
451        for _ in 0..create_count {
452            created.push(create_backend(&self.inner.base_config, &self.inner.config)?);
453        }
454
455        let mut extra = Vec::new();
456        let mut inserted = 0usize;
457        {
458            let mut state = self.inner.lock_state()?;
459            let available = self
460                .inner
461                .pool_config
462                .max_size
463                .saturating_sub(state.idle.len());
464            let keep_count = available.min(created.len());
465
466            for backend in created.drain(..keep_count) {
467                state.idle.push_back(IdleVm::new(backend));
468                inserted += 1;
469            }
470
471            extra.extend(created);
472        }
473
474        for backend in extra {
475            destroy_backend(backend, "预热超出容量");
476        }
477
478        Ok(inserted)
479    }
480
481    /// Returns a snapshot of the current pool statistics.
482    pub fn stats(&self) -> Result<VmPoolStats, PoolError> {
483        Ok(self.inner.lock_state()?.snapshot())
484    }
485}
486
487/// microVM handle borrowed from a [`VmPool`].
488///
489/// The handle is single-use with respect to ownership: once dropped, its backend is
490/// either recycled into the pool or destroyed, and the handle cannot be used again.
491pub struct PooledVm {
492    backend: Option<Backend>,
493    pool: Arc<VmPoolInner>,
494}
495
496impl PooledVm {
497    /// Executes a guest command and waits for completion.
498    pub fn execute(&mut self, cmd: &[String]) -> Result<GuestCommandResult, MicrovmError> {
499        let _span = tracing::info_span!("pool_execute").entered();
500        self.execute_with_options(cmd, GuestExecOptions::default())
501    }
502
503    /// Executes a guest command with command-level options.
504    ///
505    /// Returns [`LifecycleError::Released`] through [`MicrovmError::Lifecycle`] when
506    /// the pooled handle has already been returned to the pool.
507    pub fn execute_with_options(
508        &mut self,
509        cmd: &[String],
510        options: GuestExecOptions,
511    ) -> Result<GuestCommandResult, MicrovmError> {
512        let _span = tracing::info_span!("pool_execute").entered();
513        #[cfg(feature = "boot-profile")]
514        let execute_started_at = Instant::now();
515        let result = match self.backend.as_mut() {
516            Some(backend) => execute_backend(backend, cmd, &options),
517            None => Err(MicrovmError::Lifecycle(LifecycleError::Released(
518                "VM has been released".into(),
519            ))),
520        };
521        #[cfg(feature = "boot-profile")]
522        tracing::info!(
523            total = ?execute_started_at.elapsed(),
524            success = result.is_ok(),
525            "[pool.execute] 性能概览"
526        );
527        result
528    }
529
530    /// Executes a guest command and returns a receiver for streaming output events.
531    pub fn stream_execute(
532        &mut self,
533        cmd: &[String],
534    ) -> Result<std::sync::mpsc::Receiver<StreamEvent>, MicrovmError> {
535        let _span = tracing::info_span!("pool_execute").entered();
536        self.stream_execute_with_options(cmd, GuestExecOptions::default())
537    }
538
539    /// Executes a guest command as streaming output events with command-level options.
540    pub fn stream_execute_with_options(
541        &mut self,
542        cmd: &[String],
543        options: GuestExecOptions,
544    ) -> Result<std::sync::mpsc::Receiver<StreamEvent>, MicrovmError> {
545        let _span = tracing::info_span!("pool_execute").entered();
546        match self.backend.as_mut() {
547            Some(backend) => stream_execute_backend(backend, cmd, &options),
548            None => Err(MicrovmError::Lifecycle(LifecycleError::Released(
549                "VM has been released".into(),
550            ))),
551        }
552    }
553
554    /// Reads file contents from the borrowed guest filesystem.
555    pub fn read_file(&mut self, path: &str) -> Result<Vec<u8>, MicrovmError> {
556        match self.backend.as_mut() {
557            Some(backend) => read_file_backend(backend, path),
558            None => Err(MicrovmError::Lifecycle(LifecycleError::Released(
559                "VM has been released".into(),
560            ))),
561        }
562    }
563
564    /// Writes file contents into the borrowed guest filesystem.
565    pub fn write_file(&mut self, path: &str, data: &[u8]) -> Result<(), MicrovmError> {
566        match self.backend.as_mut() {
567            Some(backend) => write_file_backend(backend, path, data),
568            None => Err(MicrovmError::Lifecycle(LifecycleError::Released(
569                "VM has been released".into(),
570            ))),
571        }
572    }
573
574    /// Lists directory entries from the borrowed guest filesystem.
575    pub fn list_dir(&mut self, path: &str) -> Result<Vec<DirEntry>, MicrovmError> {
576        crate::guest_file_ops::list_dir(path, |cmd| self.execute(cmd))
577    }
578
579    /// Returns whether a guest path exists.
580    pub fn file_exists(&mut self, path: &str) -> Result<bool, MicrovmError> {
581        crate::guest_file_ops::file_exists(path, |cmd| self.execute(cmd))
582    }
583
584    /// Removes a file from the borrowed guest filesystem.
585    pub fn remove_file(&mut self, path: &str) -> Result<(), MicrovmError> {
586        crate::guest_file_ops::remove_file(path, |cmd| self.execute(cmd))
587    }
588
589    /// Renames or moves a file inside the borrowed guest filesystem.
590    pub fn rename(&mut self, from: &str, to: &str) -> Result<(), MicrovmError> {
591        crate::guest_file_ops::rename(from, to, |cmd| self.execute(cmd))
592    }
593
594    /// Returns guest file metadata.
595    pub fn stat(&mut self, path: &str) -> Result<FileStat, MicrovmError> {
596        crate::guest_file_ops::stat(path, |cmd| self.execute(cmd))
597    }
598
599    /// Runs one guest `PING`/`PONG` readiness probe and returns the round-trip duration.
600    pub fn ping(&mut self) -> Result<Duration, MicrovmError> {
601        match self.backend.as_mut() {
602            Some(backend) => ping_backend(backend),
603            None => Err(MicrovmError::Lifecycle(LifecycleError::Released(
604                "VM has been released".into(),
605            ))),
606        }
607    }
608
609    /// Sends a request through the host-controlled HTTP proxy for the borrowed VM.
610    pub fn http_request(&mut self, request: HttpRequest) -> Result<HttpResponse, MicrovmError> {
611        match self.backend.as_mut() {
612            Some(backend) => http_request_backend(backend, request),
613            None => Err(MicrovmError::Lifecycle(LifecycleError::Released(
614                "VM has been released".into(),
615            ))),
616        }
617    }
618
619    /// Exports a file-backed snapshot of the current borrowed VM.
620    pub fn snapshot(&self) -> Result<SandboxSnapshot, MicrovmError> {
621        match self.backend.as_ref() {
622            #[cfg(all(target_os = "linux", feature = "kvm"))]
623            Some(backend) => backend.snapshot_to_file(),
624            #[cfg(not(all(target_os = "linux", feature = "kvm")))]
625            Some(_) => Err(MicrovmError::UnsupportedPlatform),
626            None => Err(MicrovmError::Lifecycle(LifecycleError::Released(
627                "VM has been released".into(),
628            ))),
629        }
630    }
631}
632
633impl Drop for PooledVm {
634    fn drop(&mut self) {
635        #[cfg(feature = "boot-profile")]
636        let drop_started_at = Instant::now();
637        if let Some(backend) = self.backend.take() {
638            self.pool.recycle(backend);
639        }
640        #[cfg(feature = "boot-profile")]
641        tracing::info!(
642            total = ?drop_started_at.elapsed(),
643            "[pool.drop] 性能概览"
644        );
645    }
646}
647
648#[cfg(all(target_os = "linux", feature = "kvm"))]
649type Backend = KvmBackend;
650
651#[cfg(not(all(target_os = "linux", feature = "kvm")))]
652struct Backend;
653
654#[cfg(all(target_os = "linux", feature = "kvm"))]
655fn ensure_pool_supported() -> Result<(), MicrovmError> {
656    Ok(())
657}
658
659#[cfg(not(all(target_os = "linux", feature = "kvm")))]
660fn ensure_pool_supported() -> Result<(), MicrovmError> {
661    Err(MicrovmError::UnsupportedPlatform)
662}
663
664#[cfg(all(target_os = "linux", feature = "kvm"))]
665fn create_backend(
666    base_config: &SandboxConfig,
667    config: &MicrovmConfig,
668) -> Result<Backend, MicrovmError> {
669    let mut backend = KvmBackend::create_vm(base_config.clone(), config.clone())?;
670    let exit_reason = backend.boot()?;
671    if exit_reason != KvmExitReason::Io || !backend.is_guest_ready() {
672        return Err(MicrovmError::Backend(format!(
673            "预热 VM 后 guest 未进入 READY 状态: {exit_reason:?}"
674        )));
675    }
676    backend.clear_pool_artifacts();
677    Ok(backend)
678}
679
680#[cfg(not(all(target_os = "linux", feature = "kvm")))]
681fn create_backend(
682    _base_config: &SandboxConfig,
683    _config: &MicrovmConfig,
684) -> Result<Backend, MicrovmError> {
685    Err(MicrovmError::UnsupportedPlatform)
686}
687
688#[cfg(all(target_os = "linux", feature = "kvm"))]
689fn health_check_backend(
690    backend: &mut Backend,
691    health_check_command: &[String],
692) -> Result<bool, MicrovmError> {
693    let result = backend.run_command(health_check_command)?;
694    Ok(!result.timed_out && result.exit_code == Some(0))
695}
696
697#[cfg(not(all(target_os = "linux", feature = "kvm")))]
698fn health_check_backend(
699    _backend: &mut Backend,
700    _health_check_command: &[String],
701) -> Result<bool, MicrovmError> {
702    Err(MicrovmError::UnsupportedPlatform)
703}
704
705#[cfg(all(target_os = "linux", feature = "kvm"))]
706fn destroy_backend(mut backend: Backend, reason: &str) {
707    if let Err(err) = backend.shutdown() {
708        tracing::warn!("销毁 VM 失败 ({reason}): {err}");
709    }
710}
711
712#[cfg(not(all(target_os = "linux", feature = "kvm")))]
713fn destroy_backend(_backend: Backend, _reason: &str) {}
714
715#[cfg(all(target_os = "linux", feature = "kvm"))]
716fn destroy_idle_entry(entry: IdleVm, reason: &str) {
717    destroy_backend(entry.backend, reason);
718}
719
720#[cfg(not(all(target_os = "linux", feature = "kvm")))]
721fn destroy_idle_entry(_entry: IdleVm, _reason: &str) {}
722
723#[cfg(all(target_os = "linux", feature = "kvm"))]
724fn execute_backend(
725    backend: &mut Backend,
726    cmd: &[String],
727    options: &GuestExecOptions,
728) -> Result<GuestCommandResult, MicrovmError> {
729    backend.run_command_with_options(cmd, options)
730}
731
732#[cfg(all(target_os = "linux", feature = "kvm"))]
733fn stream_execute_backend(
734    backend: &mut Backend,
735    cmd: &[String],
736    options: &GuestExecOptions,
737) -> Result<std::sync::mpsc::Receiver<StreamEvent>, MicrovmError> {
738    backend.run_command_streaming_with_options(cmd, options)
739}
740
741#[cfg(not(all(target_os = "linux", feature = "kvm")))]
742fn execute_backend(
743    _backend: &mut Backend,
744    _cmd: &[String],
745    _options: &GuestExecOptions,
746) -> Result<GuestCommandResult, MicrovmError> {
747    Err(MicrovmError::UnsupportedPlatform)
748}
749
750#[cfg(not(all(target_os = "linux", feature = "kvm")))]
751fn stream_execute_backend(
752    _backend: &mut Backend,
753    _cmd: &[String],
754    _options: &GuestExecOptions,
755) -> Result<std::sync::mpsc::Receiver<StreamEvent>, MicrovmError> {
756    Err(MicrovmError::UnsupportedPlatform)
757}
758
759#[cfg(all(target_os = "linux", feature = "kvm"))]
760fn read_file_backend(backend: &mut Backend, path: &str) -> Result<Vec<u8>, MicrovmError> {
761    backend.read_file(path)
762}
763
764#[cfg(not(all(target_os = "linux", feature = "kvm")))]
765fn read_file_backend(_backend: &mut Backend, _path: &str) -> Result<Vec<u8>, MicrovmError> {
766    Err(MicrovmError::UnsupportedPlatform)
767}
768
769#[cfg(all(target_os = "linux", feature = "kvm"))]
770fn write_file_backend(backend: &mut Backend, path: &str, data: &[u8]) -> Result<(), MicrovmError> {
771    backend.write_file(path, data)
772}
773
774#[cfg(all(target_os = "linux", feature = "kvm"))]
775fn ping_backend(backend: &mut Backend) -> Result<Duration, MicrovmError> {
776    backend.ping()
777}
778
779#[cfg(not(all(target_os = "linux", feature = "kvm")))]
780fn write_file_backend(
781    _backend: &mut Backend,
782    _path: &str,
783    _data: &[u8],
784) -> Result<(), MicrovmError> {
785    Err(MicrovmError::UnsupportedPlatform)
786}
787
788#[cfg(not(all(target_os = "linux", feature = "kvm")))]
789fn ping_backend(_backend: &mut Backend) -> Result<Duration, MicrovmError> {
790    Err(MicrovmError::UnsupportedPlatform)
791}
792
793#[cfg(all(target_os = "linux", feature = "kvm"))]
794fn http_request_backend(
795    backend: &mut Backend,
796    request: HttpRequest,
797) -> Result<HttpResponse, MicrovmError> {
798    backend.http_request(request)
799}
800
801#[cfg(not(all(target_os = "linux", feature = "kvm")))]
802fn http_request_backend(
803    _backend: &mut Backend,
804    _request: HttpRequest,
805) -> Result<HttpResponse, MicrovmError> {
806    Err(MicrovmError::UnsupportedPlatform)
807}
808
809#[cfg(all(target_os = "linux", feature = "kvm"))]
810fn backend_is_reusable(backend: &Backend) -> bool {
811    backend.is_guest_ready()
812}
813
814#[cfg(not(all(target_os = "linux", feature = "kvm")))]
815fn backend_is_reusable(_backend: &Backend) -> bool {
816    false
817}
818
819#[cfg(all(target_os = "linux", feature = "kvm"))]
820fn clear_backend_artifacts(backend: &mut Backend) {
821    backend.clear_pool_artifacts();
822}
823
824#[cfg(not(all(target_os = "linux", feature = "kvm")))]
825fn clear_backend_artifacts(_backend: &mut Backend) {}