use std::time::Duration;
use tracing::{debug, info};
use super::ComputerFlows;
use crate::error::VmmError;
use crate::lifecycle::effect::ReleaseScope;
use crate::lifecycle::tasks::checkpoint::{CheckpointFailure, CheckpointRequest, checkpoint_impl};
use crate::lifecycle::tasks::pause::release_for_pause;
use crate::lifecycle::tasks::release::{release_everything, release_runtime_resources};
use crate::lifecycle::tasks::{CaptureSpec, Drain, TaskFailure, TaskResult};
use crate::sandbox::pause::PAUSE_SNAPSHOT_NAME;
use crate::sandbox::{CheckpointInfo, SandboxState};
const DRAIN_POLL: Duration = Duration::from_millis(100);
impl ComputerFlows {
pub(super) async fn capture(
&self,
hold: bool,
spec: Option<CaptureSpec>,
) -> TaskResult<CheckpointInfo> {
let request = match spec {
Some(spec) => CheckpointRequest {
name: spec.name,
labels: spec.labels,
expected_state: SandboxState::Ready,
resume_after: !hold,
},
None => CheckpointRequest {
name: PAUSE_SNAPSHOT_NAME.to_owned(),
labels: std::collections::HashMap::new(),
expected_state: SandboxState::Pausing,
resume_after: !hold,
},
};
checkpoint_impl(&self.computer, &self.services.snapshots, &self.id, request)
.await
.map_err(|failure| match failure {
CheckpointFailure::Frozen(error) => TaskFailure::frozen(error),
CheckpointFailure::Recoverable(error) => TaskFailure::recoverable(error),
})
}
pub(super) async fn stop_vm(&self, budget: Duration, drain: Drain) -> TaskResult {
let deadline = tokio::time::Instant::now() + budget;
let handle = self.computer.lock().unwrap().handle.clone();
if let Drain::Until { since } = drain {
while tokio::time::Instant::now() < deadline {
if self.computer.lock().unwrap().last_exited_at != since {
break;
}
tokio::time::sleep(DRAIN_POLL).await;
}
}
if let Some(handle) = handle {
let remaining = deadline
.checked_duration_since(tokio::time::Instant::now())
.unwrap_or(Duration::from_secs(1))
.max(Duration::from_secs(1));
handle
.shutdown(arcbox_vm_driver::ShutdownMode::Graceful { timeout: remaining })
.await
.map_err(|error| {
TaskFailure::recoverable(VmmError::Process(format!(
"shut down computer {}: {error}",
self.id
)))
})?;
}
self.release_scope(ReleaseScope::Runtime).await?;
info!(sandbox_id = %self.id, "computer stopped");
Ok(())
}
pub(super) async fn detach_vm(&self) -> TaskResult {
let handle = self.computer.lock().unwrap().handle.clone();
let Some(handle) = handle else {
debug!(sandbox_id = %self.id, "no vm to hand to the next process");
return Ok(());
};
let Some(detach) = handle.detach() else {
debug!(sandbox_id = %self.id, "the driver holds its vms in-process; nothing to hand over");
return Ok(());
};
detach.detach().await.map_err(|error| {
TaskFailure::recoverable(VmmError::Process(format!(
"hand computer {} to the next process: {error}",
self.id
)))
})?;
info!(sandbox_id = %self.id, "handed the computer's vm to the next process");
Ok(())
}
pub(super) async fn release_scope(&self, scope: ReleaseScope) -> TaskResult {
let services = &self.services;
let outcome = match scope {
ReleaseScope::Runtime => {
release_runtime_resources(
&self.id,
&self.computer,
&services.network,
&services.cow_manager,
)
.await
}
ReleaseScope::KeepDisk if services.config.firecracker.jailer.is_none() => {
Err(VmmError::Config(
"computer pause requires jailer isolation; direct mode cannot resume".into(),
))
}
ReleaseScope::KeepDisk => {
release_for_pause(
&self.id,
&self.computer,
&services.config,
&services.cow_manager,
&*services.network,
)
.await
}
ReleaseScope::Full => {
release_everything(
&self.id,
&self.computer,
&services.network,
&services.config,
&services.cow_manager,
&services.snapshots,
)
.await
}
};
outcome.map_err(TaskFailure::recoverable)
}
}