pub struct TaskExecutor { /* private fields */ }Expand description
Task executor for managing process lifecycle
TaskExecutor is the main entry point for executing system processes with real-time
event monitoring, timeout management, and cross-platform process control.
It coordinates process spawning, I/O handling, and termination through an event-driven
architecture built on tokio.
§Examples
§Basic Process Execution
use tcrm_task::tasks::{config::TaskConfig, tokio::executor::TaskExecutor};
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!"]);
config.validate()?;
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 {
tcrm_task::tasks::event::TaskEvent::Output { line, .. } => {
println!("Output: {}", line);
}
tcrm_task::tasks::event::TaskEvent::Stopped { .. } => break,
_ => {}
}
}
Ok(())
}§Process with Timeout and Termination
use tcrm_task::tasks::{
config::TaskConfig,
tokio::executor::TaskExecutor,
control::TaskControl,
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", "10"])
.timeout_ms(5000);
#[cfg(unix)]
let config = TaskConfig::new("sleep")
.args(["10"])
.timeout_ms(5000);
let (tx, mut rx) = mpsc::channel(100);
let mut executor = TaskExecutor::new(config, tx);
executor.coordinate_start().await?;
// Terminate after 2 seconds
tokio::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let _ = executor.terminate_task(TaskTerminateReason::UserRequested);
});
while let Some(envelope) = rx.recv().await {
if matches!(envelope.event, tcrm_task::tasks::event::TaskEvent::Stopped { .. }) {
break;
}
}
Ok(())
}Implementations§
Source§impl TaskExecutor
impl TaskExecutor
Sourcepub fn new(config: TaskConfig, event_tx: Sender<TaskEventEnvelope>) -> Self
pub fn new(config: TaskConfig, event_tx: Sender<TaskEventEnvelope>) -> Self
Create a new task executor with the given configuration
§Arguments
config- Validated task configuration containing command, arguments, and optionsevent_tx- Channel for sending task events
§Examples
use tcrm_task::tasks::{config::TaskConfig, tokio::executor::TaskExecutor};
use tokio::sync::mpsc;
#[cfg(windows)]
let config = TaskConfig::new("cmd").args(["/C", "dir"]);
#[cfg(unix)]
let config = TaskConfig::new("ls").args(["-la"]);
let (tx, _rx) = mpsc::channel(100);
let executor = TaskExecutor::new(config, tx);Sourcepub fn set_drop_event_tx_on_finished(&self, drop: bool)
pub fn set_drop_event_tx_on_finished(&self, drop: bool)
Configures whether to drop (close) the event channel when the task finishes.
By default, the event channel (event_tx) is dropped when the task finishes,
signaling to receivers that no more events will be sent.
This method allows you to override that behavior,
which is useful if you want to keep the event channel open for multiple tasks
or for manual control.
§Arguments
drop- Iftrue, the event channel will be dropped when the task finishes (default behavior). Iffalse, the event channel will remain open after task completion.
§Example
executor.set_drop_event_tx_on_finished(false); // Keep event channel open after task finishesSource§impl TaskExecutor
impl TaskExecutor
Sourcepub async fn send_stdin(
&mut self,
input: impl Into<String>,
) -> Result<(), TaskError>
pub async fn send_stdin( &mut self, input: impl Into<String>, ) -> Result<(), TaskError>
Sends input to the process’s stdin.
Writes the provided input to the process’s stdin stream. The input will be automatically terminated with a newline if it doesn’t already end with one.
§Arguments
input- The input string to send to stdin
§Returns
Ok(())- If the input was sent successfullyErr(TaskError)- If sending fails or the task is not running
§Errors
Returns TaskError::Control if the task is not in a running state,
or TaskError::IO if writing to stdin fails
Source§impl TaskExecutor
impl TaskExecutor
Sourcepub async fn coordinate_start(&mut self) -> Result<(), TaskError>
pub async fn coordinate_start(&mut self) -> Result<(), TaskError>
Start execution in a coordinated async event loop.
This is the main execution method that spawns the process, sets up event monitoring, and manages the complete process lifecycle. It handles stdout/stderr streaming, timeout management, termination signals, and process cleanup in a coordinated async event loop using `tokio::select!``.
§Arguments
event_tx- Channel sender for emitting [TaskEvent]s during process execution
§Returns
Ok(())- Process coordination started successfullyErr(TaskError)- Configuration validation or process spawning failed
§Errors
Returns TaskError for:
TaskError::InvalidConfiguration- Configuration validation failedTaskError::IO- Process spawning failedTaskError::Handle- Process handle or watcher setup failed
§Events Emitted
During execution, the following events are sent via event_tx:
- [
TaskEvent::Started] - Process spawned successfully - [
TaskEvent::Output] - Lines from stdout/stderr - [
TaskEvent::Ready] - Ready indicator detected (if configured) - [
TaskEvent::Stopped] - Process completed with exit code - [
TaskEvent::Error] - Errors during execution
§Examples
§Basic Usage
use tcrm_task::tasks::{config::TaskConfig, tokio::executor::TaskExecutor};
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", "test"]);
#[cfg(unix)]
let config = TaskConfig::new("echo").args(["test"]);
config.validate()?;
let (tx, mut rx) = mpsc::channel(100);
let mut executor = TaskExecutor::new(config, tx);
// Start coordination - returns immediately, process runs in background
executor.coordinate_start().await?;
// Process events until completion
while let Some(envelope) = rx.recv().await {
println!("Event: {:?}", envelope.event);
if matches!(envelope.event, tcrm_task::tasks::event::TaskEvent::Stopped { .. }) {
break;
}
}
Ok(())
}§With Ready Indicator
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 ready"])
.ready_indicator("Server ready")
.ready_indicator_source(StreamSource::Stdout);
#[cfg(unix)]
let config = TaskConfig::new("echo")
.args(["Server ready"])
.ready_indicator("Server ready")
.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!("Process is ready!");
// Can now interact with the running process
}
TaskEvent::Stopped { .. } => break,
_ => {}
}
}
Ok(())
}