use std::sync::Arc;
use std::time::Duration;
use crate::{exec::ExecOutcome, Error, Image, PooledVm, Vm, VmConfig};
#[derive(Clone)]
pub struct AsyncImage {
inner: Arc<Image>,
}
impl AsyncImage {
pub async fn from_snapshot(
path: impl Into<std::path::PathBuf> + Send + 'static,
) -> Result<Self, Error> {
let inner = tokio::task::spawn_blocking(move || Image::from_snapshot(path))
.await
.map_err(|e| Error::vm_msg(format!("tokio join: {e}")))??;
Ok(Self { inner: Arc::new(inner) })
}
pub async fn start(&self, config: VmConfig) -> Result<AsyncVm, Error> {
let img = Arc::clone(&self.inner);
let vm = tokio::task::spawn_blocking(move || img.start(&config))
.await
.map_err(|e| Error::vm_msg(format!("tokio join: {e}")))??;
Ok(AsyncVm { inner: Arc::new(vm) })
}
pub async fn acquire(&self) -> Result<AsyncPooledVm, Error> {
self.acquire_with(VmConfig::new()).await
}
pub async fn acquire_with(&self, config: VmConfig) -> Result<AsyncPooledVm, Error> {
let img = Arc::clone(&self.inner);
let pooled = tokio::task::spawn_blocking({
let img = Arc::clone(&img);
move || -> Result<PooledVm<'static>, Error> {
let pooled: PooledVm<'_> = img.acquire_with(&config)?;
Ok(unsafe { std::mem::transmute::<PooledVm<'_>, PooledVm<'static>>(pooled) })
}
})
.await
.map_err(|e| Error::vm_msg(format!("tokio join: {e}")))??;
Ok(AsyncPooledVm {
inner: Some(pooled),
_image_keepalive: img,
})
}
}
#[derive(Clone)]
pub struct AsyncVm {
inner: Arc<Vm>,
}
impl AsyncVm {
pub async fn write_file(&self, path: String, bytes: Vec<u8>) -> std::io::Result<()> {
let vm = Arc::clone(&self.inner);
tokio::task::spawn_blocking(move || vm.write_file(&path, &bytes))
.await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
}
pub async fn read_file(&self, path: String) -> std::io::Result<Vec<u8>> {
let vm = Arc::clone(&self.inner);
tokio::task::spawn_blocking(move || vm.read_file(&path))
.await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
}
pub async fn exec_output(
&self,
argv: Vec<String>,
env: Vec<(String, String)>,
timeout: Option<Duration>,
) -> std::io::Result<ExecOutcome> {
let vm = Arc::clone(&self.inner);
tokio::task::spawn_blocking(move || {
let mut b = vm.exec_builder().argv(argv);
for (k, v) in env {
b = b.env(k, v);
}
if let Some(t) = timeout {
b = b.timeout(t);
}
b.output()
})
.await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
}
}
pub struct AsyncPooledVm {
inner: Option<PooledVm<'static>>,
_image_keepalive: Arc<Image>,
}
impl AsyncPooledVm {
pub async fn exec_output(
&self,
argv: Vec<String>,
env: Vec<(String, String)>,
timeout: Option<Duration>,
) -> std::io::Result<ExecOutcome> {
let mut b = self
.inner
.as_ref()
.expect("AsyncPooledVm used after drop")
.exec_builder()
.argv(argv);
for (k, v) in env {
b = b.env(k, v);
}
if let Some(t) = timeout {
b = b.timeout(t);
}
tokio::task::spawn_blocking(move || b.output())
.await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
}
pub async fn write_file(&self, path: String, bytes: Vec<u8>) -> std::io::Result<()> {
let exec_path = self
.inner
.as_ref()
.expect("AsyncPooledVm used after drop")
.exec_path()
.to_path_buf();
tokio::task::spawn_blocking(move || {
let body = serde_json::json!({
"action": "write_file",
"path": path,
"data_b64": crate::api::b64_encode(&bytes),
});
crate::exec::send_control(&exec_path, &body)
})
.await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
}
pub async fn read_file(&self, path: String) -> std::io::Result<Vec<u8>> {
let exec_path = self
.inner
.as_ref()
.expect("AsyncPooledVm used after drop")
.exec_path()
.to_path_buf();
tokio::task::spawn_blocking(move || {
let body = serde_json::json!({
"action": "read_file",
"path": path,
});
let ack = crate::exec::send_control_with_ack(
&exec_path,
&body,
Some(Duration::from_secs(30)),
)?;
let s = ack
.get("data_b64")
.and_then(|v| v.as_str())
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"read_file: agent ack missing data_b64",
)
})?;
crate::api::b64_decode(s)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
})
.await
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
}
}
impl Drop for AsyncPooledVm {
fn drop(&mut self) {
let _ = self.inner.take();
}
}