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}