//! Warm sandbox pool: pre-provisioned containers absorb the first-exec //! latency. Real Docker — the pool fills in the background, an exec //! takes a sandbox from it, and the warmer restores the target. use std::process::Command; use std::sync::Arc; use std::time::Duration; use cm_domain::AgentId; use cm_runtime::SandboxManager; use cm_sandbox::DockerDriver; const IMAGE: &str = "clawmates/agent-base:dev"; fn ensure_image() { let exists = Command::new("docker") .args(["image", "inspect", IMAGE]) .output() .expect("docker available") .status .success(); if !exists { let root = env!("CARGO_MANIFEST_DIR"); let status = Command::new("docker") .args([ "build", "-t", IMAGE, "-f", &format!("{root}/../../images/agent-base/Dockerfile"), &format!("{root}/../../images/agent-base"), ]) .status() .expect("docker build runs"); assert!(status.success()); } } async fn pool_reaches(manager: &SandboxManager, target: usize) { for _ in 0..120 { if manager.pool_size().await == target { return; } tokio::time::sleep(Duration::from_millis(250)).await; } panic!( "pool never reached {target} (now {})", manager.pool_size().await ); } #[tokio::test] async fn the_pool_prefills_assigns_and_refills() { ensure_image(); let driver: Arc = Arc::new(DockerDriver::connect().expect("docker reachable")); let manager = Arc::new(SandboxManager::new(driver, IMAGE)).warm(2); // The warmer fills the pool without any exec happening. pool_reaches(&manager, 2).await; // An exec is served from the pool — and works. let agent = AgentId::new(); let result = manager.exec(agent, "id -u").await.unwrap(); assert_eq!(result.stdout.trim(), "10001"); // The warmer restores the target while the agent keeps its sandbox. pool_reaches(&manager, 2).await; let again = manager.exec(agent, "echo still-mine").await.unwrap(); assert_eq!(again.stdout.trim(), "still-mine"); assert_eq!( manager.pool_size().await, 2, "reuse must not drain the pool" ); // Shutdown destroys assigned AND pooled sandboxes. manager.shutdown().await; assert_eq!(manager.pool_size().await, 0); }