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