1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct VmPoolConfig {
25 pub min_size: usize,
27 pub max_size: usize,
29 pub max_idle_duration: Duration,
31 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#[derive(Debug, Clone, Default, PartialEq, Eq)]
48pub struct VmPoolStats {
49 pub hit_count: u64,
51 pub miss_count: u64,
53 pub evict_count: u64,
55 pub idle_count: usize,
57 pub in_use_count: usize,
59}
60
61#[derive(Debug, Error)]
63pub enum PoolError {
64 #[error("invalid pool config: min_size={min_size}, max_size={max_size}")]
66 InvalidConfig {
67 min_size: usize,
69 max_size: usize,
71 },
72
73 #[error("warm pool state lock poisoned")]
75 StatePoisoned,
76
77 #[error(transparent)]
79 Microvm(
80 #[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 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#[derive(Clone)]
319pub struct VmPool {
320 inner: Arc<VmPoolInner>,
321}
322
323impl VmPool {
324 pub fn new(config: MicrovmConfig, pool_config: VmPoolConfig) -> Result<Self, PoolError> {
326 Self::new_with_base(SandboxConfig::default(), config, pool_config)
327 }
328
329 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 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 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 pub fn stats(&self) -> Result<VmPoolStats, PoolError> {
483 Ok(self.inner.lock_state()?.snapshot())
484 }
485}
486
487pub struct PooledVm {
492 backend: Option<Backend>,
493 pool: Arc<VmPoolInner>,
494}
495
496impl PooledVm {
497 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 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 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 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 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 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 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 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 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 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 pub fn stat(&mut self, path: &str) -> Result<FileStat, MicrovmError> {
596 crate::guest_file_ops::stat(path, |cmd| self.execute(cmd))
597 }
598
599 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 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 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) {}