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(envelope) = rx.recv().await {
44///         match envelope.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(envelope) = rx.recv().await {
98///         match envelope.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/// Envelope for a task event, associating an event with a unique identifier.
220///
221/// `TaskEventEnvelope` is used to wrap a `TaskEvent` with an associated `id`,
222/// which typically represents the logical task or job this event belongs to.
223/// This is useful in systems where multiple tasks are running concurrently and
224/// events from different tasks need to be distinguished or routed by their id.
225///
226/// # Fields
227/// * `id` - Unique identifier for the task or job (e.g., UUID, name, or handle)
228/// * `event` - The actual event describing a state change or output for the task
229#[derive(Debug, Clone, PartialEq)]
230pub struct TaskEventEnvelope {
231    /// Unique identifier for the task or job (e.g., UUID, name)
232    pub id: Option<String>,
233    /// The actual event describing a state change or output for the task
234    pub event: TaskEvent,
235}
236
237/// Reason why a task stopped executing
238///
239/// Provides detailed information about why a process completed,
240/// whether due to natural completion, termination, or error.
241///
242/// # Exit Code Relationship
243///
244/// - `Finished`: Process completed naturally - exit code is `Some(code)`
245/// - `Terminated(_)`: Process was killed - exit code is `None`
246/// - `Error(_)`: Process encountered an error - exit code behavior varies
247///
248/// # Examples
249///
250/// ```rust
251/// use tcrm_task::tasks::{event::TaskStopReason, event::TaskTerminateReason};
252///
253/// // Natural completion
254/// let reason = TaskStopReason::Finished;
255///
256/// // Terminated due to timeout
257/// let reason = TaskStopReason::Terminated(TaskTerminateReason::Timeout);
258///
259/// // Terminated due to error
260/// let reason = TaskStopReason::Error(tcrm_task::tasks::error::TaskError::IO("Process crashed".to_string()));
261/// ```
262#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
263#[derive(Debug, Clone, PartialEq)]
264pub enum TaskStopReason {
265    /// Process completed normally with an exit code
266    ///
267    /// The process ran to completion and exited naturally.
268    /// Exit code will be `Some(code)` in the `TaskEvent::Stopped` event.
269    Finished,
270
271    /// Process was terminated for a specific reason
272    ///
273    /// The process was forcefully killed before natural completion.
274    /// Exit code will be `None` in the `TaskEvent::Stopped` event.
275    Terminated(TaskTerminateReason),
276
277    /// Process stopped due to an error
278    ///
279    /// An error occurred during execution or process management.
280    /// Exit code behavior varies depending on the type of error.
281    Error(TaskError),
282}
283
284/// Reason for terminating a running task
285///
286/// Provides context about why a task termination was requested,
287/// enabling appropriate cleanup and response handling.
288///
289/// # Examples
290///
291/// ## Timeout Termination
292/// ```rust
293/// use tcrm_task::tasks::{
294///     config::TaskConfig,
295///     tokio::executor::TaskExecutor,
296///     event::TaskTerminateReason
297/// };
298/// use tokio::sync::mpsc;
299///
300/// #[tokio::main]
301/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
302///     #[cfg(windows)]
303///     let config = TaskConfig::new("cmd").args(["/C", "timeout", "/t", "5"]); // 5 second sleep
304///     #[cfg(unix)]
305///     let config = TaskConfig::new("sleep").args(["5"]); // 5 second sleep
306///     
307///     let (tx, _rx) = mpsc::channel(100);
308///     let mut executor = TaskExecutor::new(config, tx);
309///     
310///     executor.coordinate_start().await?;
311///     
312///     // Terminate after 1 second
313///     tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
314///     
315///     Ok(())
316/// }
317/// ```
318///
319/// ## Cleanup Termination
320/// ```rust
321/// use tcrm_task::tasks::{
322///     config::TaskConfig,
323///     tokio::executor::TaskExecutor,
324///     event::TaskTerminateReason
325/// };
326/// use tokio::sync::mpsc;
327/// use crate::tcrm_task::tasks::control::TaskControl;
328///
329/// #[tokio::main]
330/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
331///     #[cfg(windows)]
332///     let config = TaskConfig::new("cmd").args(["/C", "echo", "running"]);
333///     #[cfg(unix)]
334///     let config = TaskConfig::new("echo").args(["running"]);
335///     
336///     let (tx, _rx) = mpsc::channel(100);
337///     let mut executor = TaskExecutor::new(config, tx);
338///     
339///     executor.coordinate_start().await?;
340///     
341///     let reason = TaskTerminateReason::UserRequested;
342///     executor.terminate_task(reason)?;
343///     Ok(())
344/// }
345/// ```
346#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
347#[derive(Debug, Clone, PartialEq)]
348pub enum TaskTerminateReason {
349    /// Task exceeded its configured timeout
350    ///
351    /// The process ran longer than the `timeout_ms` specified in `TaskConfig`
352    /// and was terminated to prevent runaway processes.
353    Timeout,
354
355    /// Task was terminated during cleanup operations
356    ///
357    /// Used when terminating tasks as part of application shutdown,
358    /// resource cleanup, or dependency management.
359    Cleanup,
360
361    /// Task was terminated because its dependencies finished
362    ///
363    /// Used in task orchestration scenarios where tasks depend on
364    /// other tasks and should be terminated when dependencies complete.
365    DependenciesFinished,
366
367    /// Task was terminated by explicit user request
368    ///
369    /// Used when user or external library requests the task to stop.
370    UserRequested,
371
372    /// Task was terminated due to internal error condition
373    ///
374    /// Indicates that the task encountered an unexpected error
375    /// that caused it to be terminated.
376    InternalError,
377}