tcrm-task 0.4.2

Task execution unit for TCRM project
Documentation
use std::time::SystemTime;

#[cfg(feature = "process-control")]
use crate::tasks::process::control::ProcessControlAction;
use crate::tasks::{config::StreamSource, error::TaskError};

/// Events emitted during task execution lifecycle
///
/// `TaskEvent` represents all events that occur during task execution,
/// from process start to completion. These events enable real-time monitoring
/// and event-driven programming patterns.
///
/// # Event Flow
///
/// A typical task execution emits events in this order:
/// 1. `Started` - Process has been spawned
/// 2. `Output` - Output lines from stdout/stderr (ongoing)
/// 3. `Ready` - Ready indicator detected (optional, for long-running processes)
/// 4. `Stopped` - Process has completed, with exit code and reason
///    - Exit code is `Some(code)` for natural completion
///    - Exit code is `None` for terminated processes (timeout, manual termination)
/// 5. `Error` - Error related to task execution
///
/// # Examples
///
/// ## Basic Event Processing
/// ```rust
/// use tcrm_task::tasks::{config::TaskConfig, tokio::executor::TaskExecutor, event::TaskEvent};
/// use tokio::sync::mpsc;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     #[cfg(windows)]
///     let config = TaskConfig::new("cmd").args(["/C", "echo", "hello", "world"]);
///     #[cfg(unix)]
///     let config = TaskConfig::new("echo").args(["hello", "world"]);
///     
///     let (tx, mut rx) = mpsc::channel(100);
///     let mut executor = TaskExecutor::new(config, tx);
///     
///     executor.coordinate_start().await?;
///
///     while let Some(envelope) = rx.recv().await {
///         match envelope.event {
///             TaskEvent::Started { process_id, .. } => {
///                 println!("Process started with ID: {}", process_id);
///             }
///             TaskEvent::Output { line, .. } => {
///                 println!("Output: {}", line);
///             }
///             TaskEvent::Stopped { exit_code, .. } => {
///                 match exit_code {
///                     Some(code) => println!("Process completed with code {}", code),
///                     None => println!("Process was terminated"),
///                 }
///                 break;
///             }
///             TaskEvent::Error { error } => {
///                 eprintln!("Error: {}", error);
///                 break;
///             }
///             _ => {}
///         }
///     }
///
///     Ok(())
/// }
/// ```
///
/// ## Server Ready Detection
/// ```rust
/// use tcrm_task::tasks::{
///     config::{TaskConfig, StreamSource},
///     tokio::executor::TaskExecutor,
///     event::TaskEvent
/// };
/// use tokio::sync::mpsc;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     #[cfg(windows)]
///     let config = TaskConfig::new("cmd")
///         .args(["/C", "echo", "Server listening"])
///         .ready_indicator("Server listening")
///         .ready_indicator_source(StreamSource::Stdout);
///     
///     #[cfg(unix)]
///     let config = TaskConfig::new("echo")
///         .args(["Server listening"])
///         .ready_indicator("Server listening")
///         .ready_indicator_source(StreamSource::Stdout);
///
///     let (tx, mut rx) = mpsc::channel(100);
///     let mut executor = TaskExecutor::new(config, tx);
///     executor.coordinate_start().await?;
///
///     while let Some(envelope) = rx.recv().await {
///         match envelope.event {
///             TaskEvent::Ready => {
///                 println!("Server is ready for requests!");
///                 // Server is now ready - can start sending requests
///                 break;
///             }
///             TaskEvent::Output { line, .. } => {
///                 println!("Server log: {}", line);
///             }
///             _ => {}
///         }
///     }
///
///     Ok(())
/// }
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum TaskEvent {
    /// Process has been successfully spawned and is running.
    ///
    /// This is the first event emitted after successful process spawning.
    /// The process is now running and other events will follow.
    ///
    /// # Fields
    /// * `process_id` - Operating system process ID of the spawned process
    /// * `created_at` - Timestamp when the process was created
    /// * `running_at` - Timestamp when the process started running
    Started {
        /// Operating system process ID of the spawned process
        process_id: u32,
        /// Timestamp when the process was created
        created_at: SystemTime,
        /// Timestamp when the process started running
        running_at: SystemTime,
    },

    /// Output line received from the process.
    ///
    /// Emitted for each line of output from stdout or stderr.
    /// Lines are buffered and emitted when complete (on newline).
    ///
    /// # Fields
    /// * `line` - The output line (without trailing newline)
    /// * `src` - Source stream (stdout or stderr)
    Output {
        /// The output line (without trailing newline)
        line: String,
        /// Source stream (stdout or stderr)
        src: StreamSource,
    },

    /// task has signaled it's ready to work.
    ///
    /// Only emitted for long-running processes that have a ready indicator configured.
    /// Indicates the task has completed initialization and is ready for work (e.g., server is listening).
    Ready,

    /// Task has completed execution.
    ///
    /// The task has exited and all resources have been cleaned up.
    ///
    /// # Fields
    /// * `exit_code` - Exit code from the process
    ///   - `Some(code)` - Process completed naturally with exit code
    ///   - `None` - Process was terminated (timeout, user request, etc.)
    /// * `reason` - Reason the process stopped
    /// * `finished_at` - Timestamp when the process finished
    /// * `signal` (Unix only) - Termination signal if the process was killed by a signal
    Stopped {
        /// Exit code from the process
        ///
        /// - `Some(code)` - Process completed naturally with exit code
        /// - `None` - Process was terminated (timeout, user request, etc.)
        ///
        /// Note: Terminated processes do not provide exit codes to avoid
        /// race conditions between termination and natural completion.
        exit_code: Option<i32>,
        /// Reason the process stopped
        reason: TaskStopReason,
        /// Timestamp when the process finished
        finished_at: SystemTime,

        #[cfg(unix)]
        /// Termination signal if the process was killed by a signal
        #[cfg_attr(
            feature = "serde",
            serde(
                serialize_with = "crate::tasks::signal::serialize_signal",
                deserialize_with = "crate::tasks::signal::deserialize_signal"
            )
        )]
        signal: Option<nix::sys::signal::Signal>,
    },

    /// An error occurred during task execution.
    ///
    /// Emitted when errors occur during configuration validation,
    /// process spawning, or input/output operations.
    ///
    /// # Fields
    /// * `error` - The specific error that occurred
    Error {
        /// The specific error that occurred
        error: TaskError,
    },

    /// Process control action event.
    ///
    /// Emitted when a process control action (pause, resume, stop) is performed
    /// on the running process.
    ///
    /// # Fields
    /// * `action` - The process control action that was performed (pause, resume, stop)
    #[cfg(feature = "process-control")]
    ProcessControl {
        /// The process control action that was performed (pause, resume, stop)
        action: ProcessControlAction,
    },
}

/// Envelope for a task event, associating an event with a unique identifier.
///
/// `TaskEventEnvelope` is used to wrap a `TaskEvent` with an associated `id`,
/// which typically represents the logical task or job this event belongs to.
/// This is useful in systems where multiple tasks are running concurrently and
/// events from different tasks need to be distinguished or routed by their id.
///
/// # Fields
/// * `id` - Unique identifier for the task or job (e.g., UUID, name, or handle)
/// * `event` - The actual event describing a state change or output for the task
#[derive(Debug, Clone, PartialEq)]
pub struct TaskEventEnvelope {
    /// Unique identifier for the task or job (e.g., UUID, name)
    pub id: Option<String>,
    /// The actual event describing a state change or output for the task
    pub event: TaskEvent,
}

/// Reason why a task stopped executing
///
/// Provides detailed information about why a process completed,
/// whether due to natural completion, termination, or error.
///
/// # Exit Code Relationship
///
/// - `Finished`: Process completed naturally - exit code is `Some(code)`
/// - `Terminated(_)`: Process was killed - exit code is `None`
/// - `Error(_)`: Process encountered an error - exit code behavior varies
///
/// # Examples
///
/// ```rust
/// use tcrm_task::tasks::{event::TaskStopReason, event::TaskTerminateReason};
///
/// // Natural completion
/// let reason = TaskStopReason::Finished;
///
/// // Terminated due to timeout
/// let reason = TaskStopReason::Terminated(TaskTerminateReason::Timeout);
///
/// // Terminated due to error
/// let reason = TaskStopReason::Error(tcrm_task::tasks::error::TaskError::IO("Process crashed".to_string()));
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum TaskStopReason {
    /// Process completed normally with an exit code
    ///
    /// The process ran to completion and exited naturally.
    /// Exit code will be `Some(code)` in the `TaskEvent::Stopped` event.
    Finished,

    /// Process was terminated for a specific reason
    ///
    /// The process was forcefully killed before natural completion.
    /// Exit code will be `None` in the `TaskEvent::Stopped` event.
    Terminated(TaskTerminateReason),

    /// Process stopped due to an error
    ///
    /// An error occurred during execution or process management.
    /// Exit code behavior varies depending on the type of error.
    Error(TaskError),
}

/// Reason for terminating a running task
///
/// Provides context about why a task termination was requested,
/// enabling appropriate cleanup and response handling.
///
/// # Examples
///
/// ## Timeout Termination
/// ```rust
/// use tcrm_task::tasks::{
///     config::TaskConfig,
///     tokio::executor::TaskExecutor,
///     event::TaskTerminateReason
/// };
/// use tokio::sync::mpsc;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     #[cfg(windows)]
///     let config = TaskConfig::new("cmd").args(["/C", "timeout", "/t", "5"]); // 5 second sleep
///     #[cfg(unix)]
///     let config = TaskConfig::new("sleep").args(["5"]); // 5 second sleep
///     
///     let (tx, _rx) = mpsc::channel(100);
///     let mut executor = TaskExecutor::new(config, tx);
///     
///     executor.coordinate_start().await?;
///     
///     // Terminate after 1 second
///     tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
///     
///     Ok(())
/// }
/// ```
///
/// ## Cleanup Termination
/// ```rust
/// use tcrm_task::tasks::{
///     config::TaskConfig,
///     tokio::executor::TaskExecutor,
///     event::TaskTerminateReason
/// };
/// use tokio::sync::mpsc;
/// use crate::tcrm_task::tasks::control::TaskControl;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     #[cfg(windows)]
///     let config = TaskConfig::new("cmd").args(["/C", "echo", "running"]);
///     #[cfg(unix)]
///     let config = TaskConfig::new("echo").args(["running"]);
///     
///     let (tx, _rx) = mpsc::channel(100);
///     let mut executor = TaskExecutor::new(config, tx);
///     
///     executor.coordinate_start().await?;
///     
///     let reason = TaskTerminateReason::UserRequested;
///     executor.terminate_task(reason)?;
///     Ok(())
/// }
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum TaskTerminateReason {
    /// Task exceeded its configured timeout
    ///
    /// The process ran longer than the `timeout_ms` specified in `TaskConfig`
    /// and was terminated to prevent runaway processes.
    Timeout,

    /// Task was terminated during cleanup operations
    ///
    /// Used when terminating tasks as part of application shutdown,
    /// resource cleanup, or dependency management.
    Cleanup,

    /// Task was terminated because its dependencies finished
    ///
    /// Used in task orchestration scenarios where tasks depend on
    /// other tasks and should be terminated when dependencies complete.
    DependenciesFinished,

    /// Task was terminated by explicit user request
    ///
    /// Used when user or external library requests the task to stop.
    UserRequested,

    /// Task was terminated due to internal error condition
    ///
    /// Indicates that the task encountered an unexpected error
    /// that caused it to be terminated.
    InternalError,
}