refactor wip
This commit is contained in:
@@ -1,191 +1,70 @@
|
||||
//! Hero Supervisor Worker Demo
|
||||
//!
|
||||
//! This example demonstrates the new Hero Supervisor API with:
|
||||
//! - Synchronous build() method
|
||||
//! - Asynchronous start_workers() method
|
||||
//! - Proper cleanup on program exit
|
||||
//! - Signal handling for graceful shutdown
|
||||
|
||||
use colored::*;
|
||||
use hero_supervisor::{SupervisorBuilder, ScriptType, JobStatus};
|
||||
use log::warn;
|
||||
use std::process::Stdio;
|
||||
use hero_supervisor::{SupervisorBuilder, ScriptType};
|
||||
use std::time::Duration;
|
||||
use tokio::process::{Child, Command as TokioCommand};
|
||||
use tokio::time::sleep;
|
||||
use tokio::signal;
|
||||
|
||||
/// Supervisor manages worker lifecycle and job execution
|
||||
pub struct Supervisor {
|
||||
supervisor: hero_supervisor::Supervisor,
|
||||
worker_processes: Vec<WorkerProcess>,
|
||||
redis_url: String,
|
||||
}
|
||||
|
||||
/// Represents a managed worker process
|
||||
pub struct WorkerProcess {
|
||||
id: String,
|
||||
script_type: ScriptType,
|
||||
process: Option<Child>,
|
||||
binary_path: String,
|
||||
}
|
||||
|
||||
impl Supervisor {
|
||||
/// Create a new supervisor with supervisor configuration
|
||||
pub async fn new(redis_url: String) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let supervisor = SupervisorBuilder::new()
|
||||
.caller_id("supervisor")
|
||||
.context_id("demo-context")
|
||||
.redis_url(&redis_url)
|
||||
.heroscript_workers(vec!["hero-worker-1".to_string()])
|
||||
.rhai_sal_workers(vec!["rhai-sal-worker-1".to_string()])
|
||||
.rhai_dsl_workers(vec!["rhai-dsl-worker-1".to_string()])
|
||||
.build()?;
|
||||
|
||||
Ok(Self {
|
||||
supervisor,
|
||||
worker_processes: Vec::new(),
|
||||
redis_url,
|
||||
})
|
||||
}
|
||||
|
||||
/// Start a worker for a specific script type
|
||||
pub async fn start_worker(&mut self, script_type: ScriptType, worker_binary_path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let worker_id = match script_type {
|
||||
ScriptType::HeroScript => "hero-worker-1",
|
||||
ScriptType::RhaiSAL => "rhai-sal-worker-1",
|
||||
ScriptType::RhaiDSL => "rhai-dsl-worker-1",
|
||||
};
|
||||
|
||||
println!("{}", format!("🚀 Starting {} worker: {}", script_type.as_str(), worker_id).green().bold());
|
||||
|
||||
// Check if worker binary exists
|
||||
if !std::path::Path::new(worker_binary_path).exists() {
|
||||
return Err(format!("Worker binary not found at: {}", worker_binary_path).into());
|
||||
async fn run_supervisor_demo() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("{}", "🚀 Hero Supervisor Demo - New API".cyan().bold());
|
||||
println!("{}", "Building supervisor synchronously...".yellow());
|
||||
|
||||
// Build supervisor synchronously (no .await needed)
|
||||
let supervisor = SupervisorBuilder::new()
|
||||
.redis_url("redis://127.0.0.1:6379")
|
||||
.osis_worker("/usr/local/bin/osis_worker")
|
||||
.sal_worker("/usr/local/bin/sal_worker")
|
||||
.v_worker("/usr/local/bin/v_worker")
|
||||
.python_worker("/usr/local/bin/python_worker")
|
||||
.worker_env_var("REDIS_URL", "redis://127.0.0.1:6379")
|
||||
.worker_env_var("LOG_LEVEL", "info")
|
||||
.build()?;
|
||||
|
||||
println!("{}", "✅ Supervisor built successfully!".green());
|
||||
println!("{}", "Starting workers asynchronously...".yellow());
|
||||
|
||||
// Start workers asynchronously
|
||||
supervisor.start_workers().await?;
|
||||
|
||||
println!("{}", "✅ All workers started successfully!".green());
|
||||
|
||||
// Demonstrate job creation and execution
|
||||
println!("{}", "\n📋 Creating and running test jobs...".cyan().bold());
|
||||
|
||||
// Create and run a test job
|
||||
println!("📝 Creating and running OSIS job...");
|
||||
|
||||
// Submit and run the job
|
||||
match supervisor.new_job()
|
||||
.script_type(ScriptType::OSIS)
|
||||
.script("println('Hello from OSIS worker!')")
|
||||
.timeout(Duration::from_secs(30))
|
||||
.await_response().await {
|
||||
Ok(result) => {
|
||||
println!("{}", format!("✅ Job completed successfully: {}", result).green());
|
||||
}
|
||||
|
||||
// Start the worker process
|
||||
let mut cmd = TokioCommand::new(worker_binary_path);
|
||||
cmd.arg("--worker-id").arg(worker_id)
|
||||
.arg("--redis-url").arg(&self.redis_url)
|
||||
.arg("--no-timestamp")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
let process = cmd.spawn()?;
|
||||
|
||||
let worker_process = WorkerProcess {
|
||||
id: worker_id.to_string(),
|
||||
script_type,
|
||||
process: Some(process),
|
||||
binary_path: worker_binary_path.to_string(),
|
||||
};
|
||||
|
||||
self.worker_processes.push(worker_process);
|
||||
|
||||
// Give worker time to start up
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
|
||||
println!("{}", format!("✅ Worker {} started successfully", worker_id).green());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stop all workers
|
||||
pub async fn stop_all_workers(&mut self) {
|
||||
println!("{}", "🛑 Stopping all workers...".yellow().bold());
|
||||
|
||||
for worker in &mut self.worker_processes {
|
||||
if let Some(mut process) = worker.process.take() {
|
||||
println!("Stopping worker: {}", worker.id);
|
||||
|
||||
// Try graceful shutdown first
|
||||
if let Err(e) = process.kill().await {
|
||||
warn!("Failed to kill worker {}: {}", worker.id, e);
|
||||
}
|
||||
|
||||
// Wait for process to exit
|
||||
if let Ok(status) = process.wait().await {
|
||||
println!("Worker {} exited with status: {:?}", worker.id, status);
|
||||
} else {
|
||||
warn!("Failed to wait for worker {} to exit", worker.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.worker_processes.clear();
|
||||
println!("{}", "✅ All workers stopped".green());
|
||||
}
|
||||
|
||||
/// Submit a job and return the job ID
|
||||
pub async fn submit_job(&self, script_type: ScriptType, script: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let job = self.supervisor
|
||||
.new_job()
|
||||
.script_type(script_type.clone())
|
||||
.script(script)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.build()?;
|
||||
|
||||
let job_id = job.id.clone();
|
||||
self.supervisor.create_job(&job).await?;
|
||||
|
||||
println!("{}", format!("📝 Job {} submitted for {}", job_id, script_type.as_str()).cyan());
|
||||
Ok(job_id)
|
||||
}
|
||||
|
||||
/// Wait for job completion and return result
|
||||
pub async fn wait_for_job_completion(&self, job_id: &str, timeout_duration: Duration) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
println!("{}", format!("⏳ Waiting for job {} to complete...", job_id).yellow());
|
||||
|
||||
loop {
|
||||
if start_time.elapsed() > timeout_duration {
|
||||
return Err("Job execution timeout".into());
|
||||
}
|
||||
|
||||
// Check job status using supervisor methods
|
||||
match self.supervisor.get_job_status(job_id).await {
|
||||
Ok(status) => {
|
||||
match status {
|
||||
JobStatus::Finished => {
|
||||
if let Ok(Some(result)) = self.supervisor.get_job_output(job_id).await {
|
||||
println!("{}", format!("✅ Job {} completed successfully", job_id).green());
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
JobStatus::Error => {
|
||||
return Err("Job failed".into());
|
||||
}
|
||||
_ => {
|
||||
// Job still running or waiting
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Job not found or error checking status
|
||||
}
|
||||
}
|
||||
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// List all jobs
|
||||
pub async fn list_jobs(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
self.supervisor.list_jobs().await.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Clear all jobs
|
||||
pub async fn clear_all_jobs(&self) -> Result<usize, Box<dyn std::error::Error>> {
|
||||
self.supervisor.clear_all_jobs().await.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Get worker status
|
||||
pub fn get_worker_status(&self) -> Vec<(String, ScriptType, bool)> {
|
||||
self.worker_processes.iter().map(|w| {
|
||||
(w.id.clone(), w.script_type.clone(), w.process.is_some())
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Supervisor {
|
||||
fn drop(&mut self) {
|
||||
// Ensure workers are stopped when supervisor is dropped
|
||||
if !self.worker_processes.is_empty() {
|
||||
println!("{}", "⚠️ Supervisor dropping - stopping remaining workers".yellow());
|
||||
Err(e) => {
|
||||
println!("{}", format!("❌ Job failed: {}", e).red());
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for interrupt signal
|
||||
println!("{}", "\n⏳ Press Ctrl+C to shutdown gracefully...".yellow());
|
||||
signal::ctrl_c().await?;
|
||||
|
||||
println!("{}", "\n🛑 Shutdown signal received, cleaning up...".yellow().bold());
|
||||
|
||||
// Cleanup workers before exit
|
||||
supervisor.cleanup_and_shutdown().await?;
|
||||
|
||||
println!("{}", "✅ Cleanup completed. Goodbye!".green().bold());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -193,173 +72,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize logging
|
||||
env_logger::Builder::from_default_env()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.format_timestamp(None)
|
||||
.init();
|
||||
|
||||
println!("{}", "🎯 Hero Supervisor-Worker End-to-End Demo".blue().bold());
|
||||
println!("{}", "==========================================".blue());
|
||||
|
||||
println!("{}", "Hero Supervisor Demo".cyan().bold());
|
||||
println!("{}", "This demo shows the new synchronous build API".yellow());
|
||||
println!();
|
||||
|
||||
// Configuration
|
||||
let redis_url = "redis://localhost:6379".to_string();
|
||||
let worker_binary_path = "../../target/debug/worker";
|
||||
|
||||
// Check if worker binary exists
|
||||
if !std::path::Path::new(worker_binary_path).exists() {
|
||||
println!("{}", "❌ Worker binary not found!".red().bold());
|
||||
println!("Please build the worker first:");
|
||||
println!(" cd ../worker && cargo build");
|
||||
return Err("Worker binary not found".into());
|
||||
}
|
||||
|
||||
// Create supervisor
|
||||
println!("{}", "🏗️ Creating supervisor...".cyan());
|
||||
let mut supervisor = Supervisor::new(redis_url).await?;
|
||||
println!("{}", "✅ Supervisor created successfully".green());
|
||||
println!();
|
||||
|
||||
// Clear any existing jobs
|
||||
let cleared_count = supervisor.clear_all_jobs().await?;
|
||||
if cleared_count > 0 {
|
||||
println!("{}", format!("🧹 Cleared {} existing jobs", cleared_count).yellow());
|
||||
}
|
||||
|
||||
// Demo 1: Start a HeroScript worker
|
||||
println!("{}", "📋 Demo 1: Starting HeroScript Worker".blue().bold());
|
||||
println!("{}", "------------------------------------".blue());
|
||||
|
||||
supervisor.start_worker(ScriptType::HeroScript, worker_binary_path).await?;
|
||||
|
||||
// Show worker status
|
||||
let worker_status = supervisor.get_worker_status();
|
||||
println!("Active workers:");
|
||||
for (id, script_type, active) in worker_status {
|
||||
let status = if active { "🟢 Running" } else { "🔴 Stopped" };
|
||||
println!(" {} - {} ({})", id, script_type.as_str(), status);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 2: Submit and execute a simple job
|
||||
println!("{}", "📋 Demo 2: Submit and Execute Job".blue().bold());
|
||||
println!("{}", "---------------------------------".blue());
|
||||
|
||||
let script = r#"
|
||||
print("Hello from HeroScript worker!");
|
||||
let result = 42 + 8;
|
||||
print("Calculation: 42 + 8 = " + result);
|
||||
result
|
||||
"#;
|
||||
|
||||
let job_id = supervisor.submit_job(ScriptType::HeroScript, script).await?;
|
||||
|
||||
// Wait for job completion
|
||||
match supervisor.wait_for_job_completion(&job_id, Duration::from_secs(10)).await {
|
||||
Ok(result) => {
|
||||
println!("{}", format!("🎉 Job result: {}", result).green().bold());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", format!("❌ Job failed: {}", e).red());
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 3: Submit multiple jobs
|
||||
println!("{}", "📋 Demo 3: Multiple Jobs".blue().bold());
|
||||
println!("{}", "------------------------".blue());
|
||||
|
||||
let jobs = vec![
|
||||
("Job 1", r#"print("Job 1 executing"); "job1_result""#),
|
||||
("Job 2", r#"print("Job 2 executing"); 100 + 200"#),
|
||||
("Job 3", r#"print("Job 3 executing"); "hello_world""#),
|
||||
];
|
||||
|
||||
let mut job_ids = Vec::new();
|
||||
|
||||
for (name, script) in jobs {
|
||||
let job_id = supervisor.submit_job(ScriptType::HeroScript, script).await?;
|
||||
job_ids.push((name, job_id));
|
||||
println!("{} submitted: {}", name, job_ids.last().unwrap().1);
|
||||
// Run the demo
|
||||
if let Err(e) = run_supervisor_demo().await {
|
||||
eprintln!("{}", format!("Demo failed: {}", e).red().bold());
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Wait for all jobs to complete
|
||||
for (name, job_id) in job_ids {
|
||||
match supervisor.wait_for_job_completion(&job_id, Duration::from_secs(5)).await {
|
||||
Ok(result) => {
|
||||
println!("{} completed: {}", name, result);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} failed: {}", name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 4: Job management
|
||||
println!("{}", "📋 Demo 4: Job Management".blue().bold());
|
||||
println!("{}", "-------------------------".blue());
|
||||
|
||||
let all_jobs = supervisor.list_jobs().await?;
|
||||
println!("Total jobs in system: {}", all_jobs.len());
|
||||
|
||||
if !all_jobs.is_empty() {
|
||||
println!("Job IDs:");
|
||||
for (i, job_id) in all_jobs.iter().enumerate() {
|
||||
println!(" {}. {}", i + 1, job_id);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 5: Error handling
|
||||
println!("{}", "📋 Demo 5: Error Handling".blue().bold());
|
||||
println!("{}", "-------------------------".blue());
|
||||
|
||||
let error_script = r#"
|
||||
print("This job will cause an error");
|
||||
let x = undefined_variable; // This will cause an error
|
||||
x
|
||||
"#;
|
||||
|
||||
let error_job_id = supervisor.submit_job(ScriptType::HeroScript, error_script).await?;
|
||||
|
||||
match supervisor.wait_for_job_completion(&error_job_id, Duration::from_secs(5)).await {
|
||||
Ok(result) => {
|
||||
println!("Unexpected success: {}", result);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{}", format!("Expected error handled: {}", e).yellow());
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 6: Cleanup
|
||||
println!("{}", "📋 Demo 6: Cleanup".blue().bold());
|
||||
println!("{}", "-------------------".blue());
|
||||
|
||||
let final_job_count = supervisor.list_jobs().await?.len();
|
||||
println!("Jobs before cleanup: {}", final_job_count);
|
||||
|
||||
let cleared = supervisor.clear_all_jobs().await?;
|
||||
println!("Jobs cleared: {}", cleared);
|
||||
|
||||
let remaining_jobs = supervisor.list_jobs().await?.len();
|
||||
println!("Jobs after cleanup: {}", remaining_jobs);
|
||||
println!();
|
||||
|
||||
// Stop all workers
|
||||
supervisor.stop_all_workers().await;
|
||||
|
||||
println!("{}", "🎉 Demo completed successfully!".green().bold());
|
||||
println!();
|
||||
println!("{}", "Key Features Demonstrated:".blue().bold());
|
||||
println!(" ✅ Supervisor lifecycle management");
|
||||
println!(" ✅ Worker process spawning and management");
|
||||
println!(" ✅ Job submission and execution");
|
||||
println!(" ✅ Real-time job monitoring");
|
||||
println!(" ✅ Multiple job handling");
|
||||
println!(" ✅ Error handling and recovery");
|
||||
println!(" ✅ Resource cleanup");
|
||||
println!();
|
||||
println!("{}", "The supervisor successfully managed the complete worker lifecycle!".green());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Reference in New Issue
Block a user