a3s_box_runtime/local_execution/
backend.rs1use std::path::PathBuf;
4
5use a3s_box_core::{
6 ExecutionId, ExecutionManagerError, ExecutionManagerResult, ExecutionState, KillOutcome,
7};
8use async_trait::async_trait;
9use chrono::{DateTime, Utc};
10
11use crate::BoxRecord;
12
13#[derive(Debug, Clone)]
15pub struct LocalExecutionHandle {
16 pub started_at: DateTime<Utc>,
17 pub pid: Option<u32>,
18 pub pid_start_time: Option<u64>,
19 pub exec_socket_path: PathBuf,
20 pub console_log: PathBuf,
21 pub anonymous_volumes: Vec<String>,
22}
23
24impl LocalExecutionHandle {
25 pub(crate) fn validate(&self, execution_id: &ExecutionId) -> ExecutionManagerResult<()> {
26 if self.pid.is_none() && self.pid_start_time.is_some() {
27 return Err(ExecutionManagerError::Internal(format!(
28 "backend returned a PID start time without a PID for {execution_id}"
29 )));
30 }
31 if self.exec_socket_path.as_os_str().is_empty() {
32 return Err(ExecutionManagerError::Internal(format!(
33 "backend returned an empty exec socket path for {execution_id}"
34 )));
35 }
36 if self.console_log.as_os_str().is_empty() {
37 return Err(ExecutionManagerError::Internal(format!(
38 "backend returned an empty console log path for {execution_id}"
39 )));
40 }
41 Ok(())
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct LocalExecutionObservation {
48 pub state: ExecutionState,
49 pub handle: Option<LocalExecutionHandle>,
50 pub exit_code: Option<i32>,
51}
52
53impl LocalExecutionObservation {
54 pub(crate) fn validate(&self, execution_id: &ExecutionId) -> ExecutionManagerResult<()> {
55 match self.state {
56 ExecutionState::Running | ExecutionState::Paused => {
57 self.handle.as_ref().ok_or_else(|| {
58 ExecutionManagerError::Internal(format!(
59 "backend returned {:?} without runtime evidence for {execution_id}",
60 self.state
61 ))
62 })?;
63 }
64 ExecutionState::Created
65 | ExecutionState::Creating
66 | ExecutionState::Stopped
67 | ExecutionState::Failed => {}
68 }
69 if let Some(handle) = &self.handle {
70 handle.validate(execution_id)?;
71 }
72 Ok(())
73 }
74}
75
76#[async_trait]
81pub trait LocalExecutionBackend: Send + Sync {
82 async fn start(&self, record: &BoxRecord) -> ExecutionManagerResult<LocalExecutionHandle>;
83
84 async fn inspect(
85 &self,
86 record: &BoxRecord,
87 ) -> ExecutionManagerResult<LocalExecutionObservation>;
88
89 async fn pause(
90 &self,
91 record: &BoxRecord,
92 keep_memory: bool,
93 ) -> ExecutionManagerResult<LocalExecutionHandle>;
94
95 async fn resume(&self, record: &BoxRecord) -> ExecutionManagerResult<LocalExecutionHandle>;
96
97 async fn prepare_quiescent_rootfs(&self, _record: &BoxRecord) -> ExecutionManagerResult<()> {
100 Ok(())
101 }
102
103 async fn cleanup_quiescent_rootfs(&self, _record: &BoxRecord) -> ExecutionManagerResult<()> {
106 Ok(())
107 }
108
109 async fn stop_for_restart(
112 &self,
113 record: &BoxRecord,
114 _timeout_secs: Option<u64>,
115 ) -> ExecutionManagerResult<KillOutcome> {
116 self.kill(record).await
117 }
118
119 async fn kill(&self, record: &BoxRecord) -> ExecutionManagerResult<KillOutcome>;
120}