tcrm_task/tasks/
event.rs

1use std::time::SystemTime;
2
3#[cfg(feature = "process-control")]
4use crate::tasks::process::control::ProcessControlAction;
5use crate::tasks::{config::StreamSource, error::TaskError};
6
7/// Events emitted during task execution lifecycle
8///
9/// `TaskEvent` represents all events that occur during task execution,
10/// from process start to completion. These events enable real-time monitoring
11/// and event-driven programming patterns.
12///
13/// # Event Flow
14///
15/// A typical task execution emits events in this order:
16/// 1. `Started` - Process has been spawned
17/// 2. `Output` - Output lines from stdout/stderr (ongoing)
18/// 3. `Ready` - Ready indicator detected (optional, for long-running processes)
19/// 4. `Stopped` - Process has completed, with exit code and reason
20///    - Exit code is `Some(code)` for natural completion
21///    - Exit code is `None` for terminated processes (timeout, manual termination)
22/// 5. `Error` - Error related to task execution
23///
24/// # Examples
25///
26/// ## Basic Event Processing
27/// ```rust
28/// use tcrm_task::tasks::{config::TaskConfig, tokio::executor::TaskExecutor, event::TaskEvent};
29/// use tokio::sync::mpsc;
30///
31/// #[tokio::main]
32/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
33///     #[cfg(windows)]
34///     let config = TaskConfig::new("cmd").args(["/C", "echo", "hello", "world"]);
35///     #[cfg(unix)]
36///     let config = TaskConfig::new("echo").args(["hello", "world"]);
37///     
38///     let (tx, mut rx) = mpsc::channel(100);
39///     let mut executor = TaskExecutor::new(config, tx);
40///     
41///     executor.coordinate_start().await?;
42///
43///     while let Some(event) = rx.recv().await {
44///         match event {
45///             TaskEvent::Started { process_id, .. } => {
46///                 println!("Process started with ID: {}", process_id);
47///             }
48///             TaskEvent::Output { line, .. } => {
49///                 println!("Output: {}", line);
50///             }
51///             TaskEvent::Stopped { exit_code, .. } => {
52///                 match exit_code {
53///                     Some(code) => println!("Process completed with code {}", code),
54///                     None => println!("Process was terminated"),
55///                 }
56///                 break;
57///             }
58///             TaskEvent::Error { error } => {
59///                 eprintln!("Error: {}", error);
60///                 break;
61///             }
62///             _ => {}
63///         }
64///     }
65///
66///     Ok(())
67/// }
68/// ```
69///
70/// ## Server Ready Detection
71/// ```rust
72/// use tcrm_task::tasks::{
73///     config::{TaskConfig, StreamSource},
74///     tokio::executor::TaskExecutor,
75///     event::TaskEvent
76/// };
77/// use tokio::sync::mpsc;
78///
79/// #[tokio::main]
80/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
81///     #[cfg(windows)]
82///     let config = TaskConfig::new("cmd")
83///         .args(["/C", "echo", "Server listening"])
84///         .ready_indicator("Server listening")
85///         .ready_indicator_source(StreamSource::Stdout);
86///     
87///     #[cfg(unix)]
88///     let config = TaskConfig::new("echo")
89///         .args(["Server listening"])
90///         .ready_indicator("Server listening")
91///         .ready_indicator_source(StreamSource::Stdout);
92///
93///     let (tx, mut rx) = mpsc::channel(100);
94///     let mut executor = TaskExecutor::new(config, tx);
95///     executor.coordinate_start().await?;
96///
97///     while let Some(event) = rx.recv().await {
98///         match event {
99///             TaskEvent::Ready => {
100///                 println!("Server is ready for requests!");
101///                 // Server is now ready - can start sending requests
102///                 break;
103///             }
104///             TaskEvent::Output { line, .. } => {
105///                 println!("Server log: {}", line);
106///             }
107///             _ => {}
108///         }
109///     }
110///
111///     Ok(())
112/// }
113/// ```
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115#[derive(Debug, Clone, PartialEq)]
116pub enum TaskEvent {
117    /// Process has been successfully spawned and is running.
118    ///
119    /// This is the first event emitted after successful process spawning.
120    /// The process is now running and other events will follow.
121    ///
122    /// # Fields
123    /// * `process_id` - Operating system process ID of the spawned process
124    /// * `created_at` - Timestamp when the process was created
125    /// * `running_at` - Timestamp when the process started running
126    Started {
127        /// Operating system process ID of the spawned process
128        process_id: u32,
129        /// Timestamp when the process was created
130        created_at: SystemTime,
131        /// Timestamp when the process started running
132        running_at: SystemTime,
133    },
134
135    /// Output line received from the process.
136    ///
137    /// Emitted for each line of output from stdout or stderr.
138    /// Lines are buffered and emitted when complete (on newline).
139    ///
140    /// # Fields
141    /// * `line` - The output line (without trailing newline)
142    /// * `src` - Source stream (stdout or stderr)
143    Output {
144        /// The output line (without trailing newline)
145        line: String,
146        /// Source stream (stdout or stderr)
147        src: StreamSource,
148    },
149
150    /// task has signaled it's ready to work.
151    ///
152    /// Only emitted for long-running processes that have a ready indicator configured.
153    /// Indicates the task has completed initialization and is ready for work (e.g., server is listening).
154    Ready,
155
156    /// Task has completed execution.
157    ///
158    /// The task has exited and all resources have been cleaned up.
159    ///
160    /// # Fields
161    /// * `exit_code` - Exit code from the process
162    ///   - `Some(code)` - Process completed naturally with exit code
163    ///   - `None` - Process was terminated (timeout, user request, etc.)
164    /// * `reason` - Reason the process stopped
165    /// * `finished_at` - Timestamp when the process finished
166    /// * `signal` (Unix only) - Termination signal if the process was killed by a signal
167    Stopped {
168        /// Exit code from the process
169        ///
170        /// - `Some(code)` - Process completed naturally with exit code
171        /// - `None` - Process was terminated (timeout, user request, etc.)
172        ///
173        /// Note: Terminated processes do not provide exit codes to avoid
174        /// race conditions between termination and natural completion.
175        exit_code: Option<i32>,
176        /// Reason the process stopped
177        reason: TaskStopReason,
178        /// Timestamp when the process finished
179        finished_at: SystemTime,
180
181        #[cfg(unix)]
182        /// Termination signal if the process was killed by a signal
183        #[cfg_attr(
184            feature = "serde",
185            serde(
186                serialize_with = "crate::tasks::signal::serialize_signal",
187                deserialize_with = "crate::tasks::signal::deserialize_signal"
188            )
189        )]
190        signal: Option<nix::sys::signal::Signal>,
191    },
192
193    /// An error occurred during task execution.
194    ///
195    /// Emitted when errors occur during configuration validation,
196    /// process spawning, or input/output operations.
197    ///
198    /// # Fields
199    /// * `error` - The specific error that occurred
200    Error {
201        /// The specific error that occurred
202        error: TaskError,
203    },
204
205    /// Process control action event.
206    ///
207    /// Emitted when a process control action (pause, resume, stop) is performed
208    /// on the running process.
209    ///
210    /// # Fields
211    /// * `action` - The process control action that was performed (pause, resume, stop)
212    #[cfg(feature = "process-control")]
213    ProcessControl {
214        /// The process control action that was performed (pause, resume, stop)
215        action: ProcessControlAction,
216    },
217}
218
219/// Reason why a task stopped executing
220///
221/// Provides detailed information about why a process completed,
222/// whether due to natural completion, termination, or error.
223///
224/// # Exit Code Relationship
225///
226/// - `Finished`: Process completed naturally - exit code is `Some(code)`
227/// - `Terminated(_)`: Process was killed - exit code is `None`
228/// - `Error(_)`: Process encountered an error - exit code behavior varies
229///
230/// # Examples
231///
232/// ```rust
233/// use tcrm_task::tasks::{event::TaskStopReason, event::TaskTerminateReason};
234///
235/// // Natural completion
236/// let reason = TaskStopReason::Finished;
237///
238/// // Terminated due to timeout
239/// let reason = TaskStopReason::Terminated(TaskTerminateReason::Timeout);
240///
241/// // Terminated due to error
242/// let reason = TaskStopReason::Error(tcrm_task::tasks::error::TaskError::IO("Process crashed".to_string()));
243/// ```
244#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
245#[derive(Debug, Clone, PartialEq)]
246pub enum TaskStopReason {
247    /// Process completed normally with an exit code
248    ///
249    /// The process ran to completion and exited naturally.
250    /// Exit code will be `Some(code)` in the `TaskEvent::Stopped` event.
251    Finished,
252
253    /// Process was terminated for a specific reason
254    ///
255    /// The process was forcefully killed before natural completion.
256    /// Exit code will be `None` in the `TaskEvent::Stopped` event.
257    Terminated(TaskTerminateReason),
258
259    /// Process stopped due to an error
260    ///
261    /// An error occurred during execution or process management.
262    /// Exit code behavior varies depending on the type of error.
263    Error(TaskError),
264}
265
266/// Reason for terminating a running task
267///
268/// Provides context about why a task termination was requested,
269/// enabling appropriate cleanup and response handling.
270///
271/// # Examples
272///
273/// ## Timeout Termination
274/// ```rust
275/// use tcrm_task::tasks::{
276///     config::TaskConfig,
277///     tokio::executor::TaskExecutor,
278///     event::TaskTerminateReason
279/// };
280/// use tokio::sync::mpsc;
281///
282/// #[tokio::main]
283/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
284///     #[cfg(windows)]
285///     let config = TaskConfig::new("cmd").args(["/C", "timeout", "/t", "5"]); // 5 second sleep
286///     #[cfg(unix)]
287///     let config = TaskConfig::new("sleep").args(["5"]); // 5 second sleep
288///     
289///     let (tx, _rx) = mpsc::channel(100);
290///     let mut executor = TaskExecutor::new(config, tx);
291///     
292///     executor.coordinate_start().await?;
293///     
294///     // Terminate after 1 second
295///     tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
296///     
297///     Ok(())
298/// }
299/// ```
300///
301/// ## Cleanup Termination
302/// ```rust
303/// use tcrm_task::tasks::{
304///     config::TaskConfig,
305///     tokio::executor::TaskExecutor,
306///     event::TaskTerminateReason
307/// };
308/// use tokio::sync::mpsc;
309/// use crate::tcrm_task::tasks::control::TaskControl;
310///
311/// #[tokio::main]
312/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
313///     #[cfg(windows)]
314///     let config = TaskConfig::new("cmd").args(["/C", "echo", "running"]);
315///     #[cfg(unix)]
316///     let config = TaskConfig::new("echo").args(["running"]);
317///     
318///     let (tx, _rx) = mpsc::channel(100);
319///     let mut executor = TaskExecutor::new(config, tx);
320///     
321///     executor.coordinate_start().await?;
322///     
323///     let reason = TaskTerminateReason::UserRequested;
324///     executor.terminate_task(reason)?;
325///     Ok(())
326/// }
327/// ```
328#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
329#[derive(Debug, Clone, PartialEq)]
330pub enum TaskTerminateReason {
331    /// Task exceeded its configured timeout
332    ///
333    /// The process ran longer than the `timeout_ms` specified in `TaskConfig`
334    /// and was terminated to prevent runaway processes.
335    Timeout,
336
337    /// Task was terminated during cleanup operations
338    ///
339    /// Used when terminating tasks as part of application shutdown,
340    /// resource cleanup, or dependency management.
341    Cleanup,
342
343    /// Task was terminated because its dependencies finished
344    ///
345    /// Used in task orchestration scenarios where tasks depend on
346    /// other tasks and should be terminated when dependencies complete.
347    DependenciesFinished,
348
349    /// Task was terminated by explicit user request
350    ///
351    /// Used when user or external library requests the task to stop.
352    UserRequested,
353
354    /// Task was terminated due to internal error condition
355    ///
356    /// Indicates that the task encountered an unexpected error
357    /// that caused it to be terminated.
358    InternalError,
359}