Skip to main content

a3s_box_runtime/local_execution/
mod.rs

1//! Durable implementation of the backend-neutral local execution lifecycle.
2
3mod api;
4mod backend;
5mod create;
6mod logs;
7mod operations;
8mod port;
9mod record;
10mod recovery;
11mod resources;
12mod restart;
13#[cfg(unix)]
14mod session;
15mod snapshot;
16mod store;
17mod support;
18#[cfg(feature = "vm")]
19mod vm_backend;
20#[cfg(feature = "vm")]
21mod vm_process;
22
23use std::path::PathBuf;
24use std::sync::Arc;
25
26use a3s_box_core::{
27    ExecutionGeneration, ExecutionId, ExecutionManagerError, ExecutionManagerResult,
28};
29
30pub use backend::{LocalExecutionBackend, LocalExecutionHandle, LocalExecutionObservation};
31use record::{build_managed_record, status_from_record};
32use store::RuntimeUpdate;
33#[cfg(feature = "vm")]
34pub use vm_backend::VmLocalExecutionBackend;
35
36use crate::{BoxRecord, ManagedExecutionOperation, ManagedExecutionState, ManagedExecutionStore};
37
38/// Local lifecycle facade shared by service, CLI, and SDK adapters.
39#[derive(Clone)]
40pub struct LocalExecutionManager {
41    store: ManagedExecutionStore,
42    home_dir: PathBuf,
43    backend: Arc<dyn LocalExecutionBackend>,
44}
45
46impl LocalExecutionManager {
47    pub fn new(
48        state_path: impl Into<PathBuf>,
49        home_dir: impl Into<PathBuf>,
50        backend: Arc<dyn LocalExecutionBackend>,
51    ) -> Self {
52        Self {
53            store: ManagedExecutionStore::new(state_path),
54            home_dir: home_dir.into(),
55            backend,
56        }
57    }
58
59    pub fn state_path(&self) -> &std::path::Path {
60        self.store.path()
61    }
62
63    pub(super) async fn require_running_record(
64        &self,
65        execution_id: &ExecutionId,
66        generation: ExecutionGeneration,
67    ) -> ExecutionManagerResult<BoxRecord> {
68        let record = self
69            .get(execution_id)
70            .await?
71            .ok_or_else(|| ExecutionManagerError::NotFound(execution_id.clone()))?;
72        support::require_generation(&record, execution_id, generation)?;
73        if support::managed_state(&record)? != ManagedExecutionState::Running {
74            return Err(ExecutionManagerError::Conflict {
75                execution_id: execution_id.clone(),
76                message: "execution is not running".to_string(),
77            });
78        }
79        if record.exec_socket_path.as_os_str().is_empty() {
80            return Err(ExecutionManagerError::Internal(format!(
81                "execution {execution_id} has no exec endpoint"
82            )));
83        }
84        #[cfg(target_os = "linux")]
85        {
86            let pid = record
87                .pid
88                .ok_or_else(|| ExecutionManagerError::NotFound(execution_id.clone()))?;
89            if !crate::process::is_process_alive_with_identity(pid, record.pid_start_time) {
90                return Err(ExecutionManagerError::NotFound(execution_id.clone()));
91            }
92        }
93        Ok(record)
94    }
95
96    #[cfg(feature = "vm")]
97    pub fn with_vm_backend(state_path: impl Into<PathBuf>, home_dir: impl Into<PathBuf>) -> Self {
98        let home_dir = home_dir.into();
99        Self::new(
100            state_path,
101            home_dir.clone(),
102            Arc::new(VmLocalExecutionBackend::new(home_dir)),
103        )
104    }
105}
106
107#[cfg(test)]
108mod tests;