# How to Use the Execution Engine
This guide shows you how to consume and use the execution engine library to run Python scripts and other commands.
## Quick Start
### 1. Run the Example
```bash
# Run the Python execution demo
cargo run --example python_execution
# Run the general standalone example
cargo run --example standalone
```
### 2. Add as a Dependency
If you're creating a new Rust project that uses this execution engine:
**In your `Cargo.toml`:**
```toml
[dependencies]
execution-engine = { path = "../v2-execution-engine-rust" }
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }
```
## Python Script Execution - 3 Methods
### Method 1: Command::Script (Recommended)
Best for executing script files with an interpreter.
```rust
use execution_engine::*;
use std::sync::Arc;
use std::collections::HashMap;
#[tokio::main]
async fn main() {
// Create the engine
let config = ExecutionConfig::default();
let engine = Arc::new(ExecutionEngine::new(config).unwrap());
// Create execution request
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Script {
path: PathBuf::from("path/to/script.py"),
interpreter: Some("python3".to_string()),
},
env: {
let mut env = HashMap::new();
env.insert("PYTHONPATH".to_string(), "/custom/path".to_string());
env.insert("MY_VAR".to_string(), "my_value".to_string());
env
},
working_dir: Some(PathBuf::from("/working/directory")),
timeout_ms: Some(30000), // 30 seconds
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
// Execute and get execution ID
let execution_id = engine.execute(request).await.unwrap();
// Wait for completion and get result
let result = engine.wait_for_completion(execution_id).await.unwrap();
// Check result
println!("Success: {}", result.success);
println!("Exit code: {}", result.exit_code);
println!("Output:\n{}", result.stdout);
if !result.stderr.is_empty() {
println!("Errors:\n{}", result.stderr);
}
}
```
### Method 2: Command::Exec
Best for direct program execution with explicit arguments.
```rust
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Exec {
program: "python3".to_string(),
args: vec![
"script.py".to_string(),
"--arg1".to_string(),
"value1".to_string(),
"--arg2".to_string(),
"value2".to_string(),
],
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(10000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let execution_id = engine.execute(request).await.unwrap();
let result = engine.wait_for_completion(execution_id).await.unwrap();
```
### Method 3: Command::Shell
Best for simple commands or when you need shell features (pipes, redirects, etc.).
```rust
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: "python3 script.py --verbose".to_string(),
shell: "/bin/bash".to_string(),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(10000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let execution_id = engine.execute(request).await.unwrap();
let result = engine.wait_for_completion(execution_id).await.unwrap();
```
## Common Use Cases
### 1. Run Python Script with Virtual Environment
```rust
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: "source venv/bin/activate && python script.py".to_string(),
shell: "/bin/bash".to_string(),
},
// ... rest of config
};
```
### 2. Run Python with pip packages
```rust
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Exec {
program: "python3".to_string(),
args: vec!["-m".to_string(), "pip".to_string(), "install".to_string(), "requests".to_string()],
},
// ... rest of config
};
```
### 3. Check Execution Status Without Blocking
```rust
// Execute command
let execution_id = engine.execute(request).await.unwrap();
// Check status without waiting
tokio::time::sleep(Duration::from_secs(1)).await;
let status = engine.get_status(execution_id).await.unwrap();
println!("Current status: {:?}", status);
// Get result if completed (returns None if still running)
if let Some(result) = engine.get_result(execution_id).await.unwrap() {
println!("Completed with exit code: {}", result.exit_code);
}
```
### 4. Cancel a Running Execution
```rust
let execution_id = engine.execute(request).await.unwrap();
// Later, cancel it
tokio::time::sleep(Duration::from_secs(2)).await;
engine.cancel(execution_id).await.unwrap();
let result = engine.wait_for_completion(execution_id).await.unwrap();
assert_eq!(result.status, ExecutionStatus::Cancelled);
```
### 5. Real-time Output Streaming with Event Handler
```rust
use async_trait::async_trait;
// Create custom event handler
struct MyEventHandler;
#[async_trait]
impl EventHandler for MyEventHandler {
async fn handle_event(&self, event: ExecutionEvent) {
match event {
ExecutionEvent::Started { execution_id, .. } => {
println!("[{}] Started", execution_id);
}
ExecutionEvent::Stdout { execution_id, line } => {
println!("[{}] STDOUT: {}", execution_id, line);
}
ExecutionEvent::Stderr { execution_id, line } => {
println!("[{}] STDERR: {}", execution_id, line);
}
ExecutionEvent::Completed { execution_id, success, .. } => {
println!("[{}] Completed. Success: {}", execution_id, success);
}
_ => {}
}
}
}
// Create engine with event handler
let handler = Arc::new(MyEventHandler);
let engine = ExecutionEngine::new(config)
.unwrap()
.with_event_handler(handler);
```
## Configuration Options
### ExecutionConfig
```rust
let config = ExecutionConfig {
// Default timeout if not specified in request (5 minutes)
default_timeout_ms: 300_000,
// Maximum allowed timeout (1 hour)
max_timeout_ms: 3_600_000,
// Enable line-by-line streaming
stream_output: true,
// Optional log directory
log_dir: Some(PathBuf::from("/var/log/executions")),
// Max concurrent executions (semaphore limit)
max_concurrent_executions: 100,
// Memory cleanup threshold
max_in_memory_executions: 1000,
// How long to keep completed executions in memory
execution_retention_secs: 3600, // 1 hour
// Enable automatic cleanup task
enable_auto_cleanup: true,
// Max output size (10 MB)
max_output_size_bytes: 10 * 1024 * 1024,
// What to do when output is too large
oversized_output_strategy: OversizedOutputStrategy::TruncateWithWarning,
};
```
### Output Size Strategies
When output exceeds `max_output_size_bytes`:
1. **TruncateWithWarning**: Keep first N bytes, append warning
2. **FailExecution**: Fail with `ExecutionError::OutputSizeExceeded`
3. **StreamToFile**: Stream overflow to temp file (path in `stdout_overflow_file`/`stderr_overflow_file`)
## Error Handling
```rust
match engine.execute(request).await {
Ok(execution_id) => {
// Execution started successfully
match engine.wait_for_completion(execution_id).await {
Ok(result) => {
if result.success {
println!("Command succeeded");
} else {
println!("Command failed with exit code: {}", result.exit_code);
if let Some(error) = result.error {
println!("Error: {}", error);
}
}
}
Err(e) => println!("Execution error: {}", e),
}
}
Err(ExecutionError::ConcurrencyLimitReached(limit)) => {
println!("Too many concurrent executions (limit: {})", limit);
// Queue for later or return error to user
}
Err(e) => println!("Failed to start execution: {}", e),
}
```
## Integration with Tauri Desktop App
This library is designed to be consumed by a Tauri application:
```rust
// In src-tauri/src/main.rs
use execution_engine::{ExecutionEngine, ExecutionConfig};
use std::sync::Arc;
#[tokio::main]
async fn main() {
let config = ExecutionConfig::default();
let engine = Arc::new(ExecutionEngine::new(config).unwrap());
// Start cleanup task
engine.clone().start_cleanup_task();
tauri::Builder::default()
.manage(engine) // Share engine across all Tauri commands
.invoke_handler(tauri::generate_handler![
execute_python_script,
get_execution_status,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
async fn execute_python_script(
script_path: String,
engine: tauri::State<'_, Arc<ExecutionEngine>>,
) -> Result<String, String> {
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Script {
path: PathBuf::from(script_path),
interpreter: Some("python3".to_string()),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(60000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
engine.execute(request)
.await
.map(|id| id.to_string())
.map_err(|e| e.to_string())
}
```
## Testing
```bash
# Run all tests
cargo test
# Run specific test
cargo test test_executor_timeout
# Run with output visible
cargo test -- --nocapture
```
## Files Created for This Demo
1. **test-scripts/demo.py** - Sample Python script
2. **examples/python_execution.rs** - Demonstration of all 3 execution methods
3. **This guide** - HOW-TO-USE.md
## Next Steps
1. Read the [CLAUDE.md](CLAUDE.md) for detailed architecture documentation
2. Explore [src/lib.rs](src/lib.rs) for API documentation
3. Check [docs/](docs/) folder for comprehensive guides
4. Run the examples to see it in action
## Common Pitfalls
1. **Always handle `ConcurrencyLimitReached`** - Don't unwrap execute() calls
2. **Use `wait_for_completion()`** - `get_result()` returns None if not complete
3. **Timeout is not an error** - Check `result.status` for `ExecutionStatus::Timeout`
4. **Enable cleanup task** - Call `engine.start_cleanup_task()` to prevent memory leaks
Happy executing!