Skip to content

Commit 8589a5d

Browse files
authored
Merge pull request #49 from AI45Lab/fix/box-pool-snapshot-fork-fallback
fix(runtime): warm pool falls back to cold boot when snapshot-fork is unavailable
2 parents 01af700 + 20c1d2b commit 8589a5d

1 file changed

Lines changed: 88 additions & 33 deletions

File tree

src/runtime/src/pool/warm_pool.rs

Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,12 @@ pub struct WarmPool {
7575
scaler: Option<Arc<Mutex<PoolScaler>>>,
7676
/// Prometheus metrics (optional).
7777
metrics: Option<crate::prom::RuntimeMetrics>,
78-
/// Snapshot-fork template (built lazily on first fill when
78+
/// Snapshot-fork template state (built lazily on first fill when
7979
/// `config.snapshot_fork`): the file-backed RAM image + state file every other
80-
/// pool VM restores from. `None` until the first template is built.
81-
template: Arc<Mutex<Option<PoolTemplate>>>,
80+
/// pool VM restores from. Caches an `Unavailable` verdict so a build failure
81+
/// (native VM snapshot unsupported on this build) is not re-attempted on every
82+
/// fill — the pool cold-boots instead.
83+
template: Arc<Mutex<TemplateState>>,
8284
}
8385

8486
/// A built snapshot-fork template: the shared RAM image + state file that pool VMs
@@ -89,6 +91,17 @@ struct PoolTemplate {
8991
state_file: String,
9092
}
9193

94+
/// Cached state of the snapshot-fork template.
95+
enum TemplateState {
96+
/// Not built yet — the first snapshot-fork fill attempts the build.
97+
Unbuilt,
98+
/// Built and ready; pool VMs restore from it.
99+
Ready(PoolTemplate),
100+
/// The build failed (native VM snapshot is unavailable on this build/platform).
101+
/// Cached so it is not retried — `boot_or_restore` cold-boots instead.
102+
Unavailable,
103+
}
104+
92105
impl WarmPool {
93106
/// Create and start the warm pool.
94107
///
@@ -142,7 +155,7 @@ impl WarmPool {
142155
shutdown_rx,
143156
scaler,
144157
metrics: None,
145-
template: Arc::new(Mutex::new(None)),
158+
template: Arc::new(Mutex::new(TemplateState::Unbuilt)),
146159
};
147160

148161
// Initial fill
@@ -431,37 +444,87 @@ impl WarmPool {
431444
snapshot_fork: bool,
432445
box_config: &BoxConfig,
433446
event_emitter: &EventEmitter,
434-
template: &Arc<Mutex<Option<PoolTemplate>>>,
447+
template: &Arc<Mutex<TemplateState>>,
435448
) -> Result<VmManager> {
436449
if snapshot_fork {
437-
let tpl = Self::ensure_template(box_config, event_emitter, template).await?;
438-
let mut cfg = box_config.clone();
439-
cfg.snapshot_mem_file = Some(tpl.mem_file.clone());
440-
cfg.restore_from = Some(tpl.state_file.clone());
441-
cfg.snapshot_sock = None;
442-
let mut vm = VmManager::new(cfg, event_emitter.clone());
443-
vm.boot().await?;
444-
Ok(vm)
445-
} else {
446-
let mut vm = VmManager::new(box_config.clone(), event_emitter.clone());
447-
vm.boot().await?;
448-
Ok(vm)
450+
// Try the snapshot-fork template. If it can't be built (native VM
451+
// snapshot unavailable — the verdict is cached so this is attempted at
452+
// most once), fall back to a normal cold boot so the warm pool still
453+
// fills rather than failing outright.
454+
match Self::ensure_template(box_config, event_emitter, template).await {
455+
Ok(tpl) => {
456+
let mut cfg = box_config.clone();
457+
cfg.snapshot_mem_file = Some(tpl.mem_file.clone());
458+
cfg.restore_from = Some(tpl.state_file.clone());
459+
cfg.snapshot_sock = None;
460+
let mut vm = VmManager::new(cfg, event_emitter.clone());
461+
vm.boot().await?;
462+
return Ok(vm);
463+
}
464+
Err(error) => {
465+
tracing::debug!(%error, "snapshot-fork unavailable; cold-booting this pool VM");
466+
}
467+
}
449468
}
469+
let mut vm = VmManager::new(box_config.clone(), event_emitter.clone());
470+
vm.boot().await?;
471+
Ok(vm)
450472
}
451473

452-
/// Build the snapshot-fork template once (lazily): cold-boot one VM with
453-
/// file-backed RAM, snapshot it, tear down the source. Concurrent callers wait on
454-
/// the lock and reuse the first-built template.
474+
/// Get the snapshot-fork template, building it once lazily. Concurrent callers
475+
/// wait on the lock and reuse the first result — a built template OR a cached
476+
/// `Unavailable` verdict, so a failed build (native VM snapshot unsupported on
477+
/// this build) is attempted at most once rather than re-tried (and re-timed-out)
478+
/// on every pool fill. Returns `Err` when unavailable so `boot_or_restore` cold
479+
/// boots instead.
455480
async fn ensure_template(
456481
box_config: &BoxConfig,
457482
event_emitter: &EventEmitter,
458-
template: &Arc<Mutex<Option<PoolTemplate>>>,
483+
template: &Arc<Mutex<TemplateState>>,
459484
) -> Result<PoolTemplate> {
460485
let mut guard = template.lock().await;
461-
if let Some(t) = guard.as_ref() {
462-
return Ok(t.clone());
486+
match &*guard {
487+
TemplateState::Ready(t) => return Ok(t.clone()),
488+
TemplateState::Unavailable => {
489+
return Err(BoxError::PoolError(
490+
"snapshot-fork template unavailable (native VM snapshot unsupported)"
491+
.to_string(),
492+
));
493+
}
494+
TemplateState::Unbuilt => {}
463495
}
464496

497+
match Self::build_template(box_config, event_emitter).await {
498+
Ok(tpl) => {
499+
*guard = TemplateState::Ready(tpl.clone());
500+
event_emitter.emit(BoxEvent::with_string(
501+
"pool.template.built",
502+
format!(
503+
"Snapshot-fork template built for image {}",
504+
box_config.image
505+
),
506+
));
507+
Ok(tpl)
508+
}
509+
Err(error) => {
510+
// Cache the verdict and warn ONCE; the pool falls back to cold boot.
511+
tracing::warn!(
512+
%error,
513+
"snapshot-fork template build failed; marking unavailable — \
514+
the warm pool will cold-boot instead"
515+
);
516+
*guard = TemplateState::Unavailable;
517+
Err(error)
518+
}
519+
}
520+
}
521+
522+
/// Cold-boot one source VM with file-backed RAM + a trigger socket, snapshot it,
523+
/// and tear it down — leaving the RAM image + state file as the template.
524+
async fn build_template(
525+
box_config: &BoxConfig,
526+
event_emitter: &EventEmitter,
527+
) -> Result<PoolTemplate> {
465528
let dir = a3s_box_core::dirs_home().join("pool").join(format!(
466529
"tpl-{:016x}",
467530
crate::vm::fnv1a_hash(&box_config.image)
@@ -485,18 +548,10 @@ impl WarmPool {
485548
Self::trigger_snapshot(&sock, &state_file).await?;
486549
let _ = src.destroy_with_timeout(2000).await;
487550

488-
let tpl = PoolTemplate {
551+
Ok(PoolTemplate {
489552
mem_file: mem_file.to_string_lossy().into_owned(),
490553
state_file: state_file.to_string_lossy().into_owned(),
491-
};
492-
*guard = Some(tpl.clone());
493-
494-
event_emitter.emit(BoxEvent::with_string(
495-
"pool.template.built",
496-
format!("Snapshot-fork template built at {}", dir.display()),
497-
));
498-
499-
Ok(tpl)
554+
})
500555
}
501556

502557
/// Send a `snapshot <state>` request to libkrun's per-template trigger socket and

0 commit comments

Comments
 (0)