Skip to main content

a3s_box_core/traits/
session.rs

1//! Backend-neutral command, PTY, and file access for managed executions.
2
3use std::sync::Arc;
4
5use async_trait::async_trait;
6
7use crate::exec::{
8    ExecEvent, ExecOutput, ExecRequest, FileRequest, FileResponse, FilesystemRequest,
9    FilesystemResponse,
10};
11use crate::pty::PtyRequest;
12
13use super::execution::{
14    ExecutionGeneration, ExecutionId, ExecutionManagerError, ExecutionManagerResult,
15};
16
17/// Signals supported by the backend-neutral managed process channel.
18///
19/// A3S workloads always execute in a Linux guest or Linux OCI Sandbox, so the
20/// numeric values are stable even when the host itself is macOS or Windows.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum ExecutionProcessSignal {
23    Terminate,
24    Kill,
25}
26
27impl ExecutionProcessSignal {
28    pub const fn linux_number(self) -> i32 {
29        match self {
30            Self::Terminate => 15,
31            Self::Kill => 9,
32        }
33    }
34}
35
36/// Cloneable input/control side of one running execution process.
37#[async_trait]
38pub trait ExecutionProcessInput: Send + Sync {
39    async fn write_stdin(&self, data: &[u8]) -> ExecutionManagerResult<()>;
40
41    async fn close_stdin(&self) -> ExecutionManagerResult<()>;
42
43    async fn cancel(&self) -> ExecutionManagerResult<()>;
44
45    async fn send_signal(&self, signal: ExecutionProcessSignal) -> ExecutionManagerResult<()> {
46        match signal {
47            ExecutionProcessSignal::Kill => self.cancel().await,
48            ExecutionProcessSignal::Terminate => Err(ExecutionManagerError::InvalidRequest(
49                "process transport does not support graceful termination".to_string(),
50            )),
51        }
52    }
53
54    async fn resize_pty(&self, cols: u16, rows: u16) -> ExecutionManagerResult<()> {
55        let _ = (cols, rows);
56        Err(ExecutionManagerError::InvalidRequest(
57            "process does not have a PTY".to_string(),
58        ))
59    }
60}
61
62/// Event side of one running execution process.
63#[async_trait]
64pub trait ExecutionProcessStream: Send {
65    fn input(&self) -> Arc<dyn ExecutionProcessInput>;
66
67    async fn next_event(&mut self) -> ExecutionManagerResult<Option<ExecEvent>>;
68}
69
70pub type ExecutionProcess = Box<dyn ExecutionProcessStream>;
71
72/// Generation-fenced process and filesystem access shared by compatibility
73/// services and native SDK adapters.
74///
75/// Implementations must bind the underlying runtime endpoint before their
76/// final generation check. A generation change may fail an operation, but it
77/// must never redirect the operation to the replacement runtime.
78#[async_trait]
79pub trait ExecutionSessionManager: Send + Sync {
80    async fn execute(
81        &self,
82        execution_id: &ExecutionId,
83        generation: ExecutionGeneration,
84        request: ExecRequest,
85    ) -> ExecutionManagerResult<ExecOutput>;
86
87    async fn start_process(
88        &self,
89        execution_id: &ExecutionId,
90        generation: ExecutionGeneration,
91        request: ExecRequest,
92    ) -> ExecutionManagerResult<ExecutionProcess>;
93
94    async fn start_pty(
95        &self,
96        execution_id: &ExecutionId,
97        generation: ExecutionGeneration,
98        request: PtyRequest,
99    ) -> ExecutionManagerResult<ExecutionProcess>;
100
101    async fn transfer_file(
102        &self,
103        execution_id: &ExecutionId,
104        generation: ExecutionGeneration,
105        request: FileRequest,
106    ) -> ExecutionManagerResult<FileResponse>;
107
108    async fn filesystem(
109        &self,
110        _execution_id: &ExecutionId,
111        _generation: ExecutionGeneration,
112        _request: FilesystemRequest,
113    ) -> ExecutionManagerResult<FilesystemResponse> {
114        Err(ExecutionManagerError::Unavailable(
115            "this execution session does not support filesystem metadata operations".to_string(),
116        ))
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::ExecutionProcessSignal;
123
124    #[test]
125    fn managed_process_signals_use_linux_guest_numbers() {
126        assert_eq!(ExecutionProcessSignal::Terminate.linux_number(), 15);
127        assert_eq!(ExecutionProcessSignal::Kill.linux_number(), 9);
128    }
129}