initial commit
This commit is contained in:
		
							
								
								
									
										20
									
								
								core/examples/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								core/examples/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "hero_examples"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "supervisor_worker_demo"
 | 
			
		||||
path = "supervisor_worker_demo.rs"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
hero_dispatcher = { path = "../dispatcher" }
 | 
			
		||||
hero_job = { path = "../job" }
 | 
			
		||||
tokio = { version = "1.0", features = ["full"] }
 | 
			
		||||
redis = { version = "0.25", features = ["tokio-comp"] }
 | 
			
		||||
serde_json = "1.0"
 | 
			
		||||
log = "0.4"
 | 
			
		||||
env_logger = "0.10"
 | 
			
		||||
colored = "2.0"
 | 
			
		||||
uuid = { version = "1.0", features = ["v4"] }
 | 
			
		||||
chrono = { version = "0.4", features = ["serde"] }
 | 
			
		||||
							
								
								
									
										365
									
								
								core/examples/supervisor_worker_demo.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										365
									
								
								core/examples/supervisor_worker_demo.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,365 @@
 | 
			
		||||
use colored::*;
 | 
			
		||||
use hero_dispatcher::{DispatcherBuilder, ScriptType, JobStatus};
 | 
			
		||||
use log::warn;
 | 
			
		||||
use std::process::Stdio;
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
use tokio::process::{Child, Command as TokioCommand};
 | 
			
		||||
use tokio::time::sleep;
 | 
			
		||||
 | 
			
		||||
/// Supervisor manages worker lifecycle and job execution
 | 
			
		||||
pub struct Supervisor {
 | 
			
		||||
    dispatcher: hero_dispatcher::Dispatcher,
 | 
			
		||||
    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 dispatcher configuration
 | 
			
		||||
    pub async fn new(redis_url: String) -> Result<Self, Box<dyn std::error::Error>> {
 | 
			
		||||
        let dispatcher = DispatcherBuilder::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 {
 | 
			
		||||
            dispatcher,
 | 
			
		||||
            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());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 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.dispatcher
 | 
			
		||||
            .new_job()
 | 
			
		||||
            .script_type(script_type.clone())
 | 
			
		||||
            .script(script)
 | 
			
		||||
            .timeout(Duration::from_secs(30))
 | 
			
		||||
            .build()?;
 | 
			
		||||
 | 
			
		||||
        let job_id = job.id.clone();
 | 
			
		||||
        self.dispatcher.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 dispatcher methods
 | 
			
		||||
            match self.dispatcher.get_job_status(job_id).await {
 | 
			
		||||
                Ok(status) => {
 | 
			
		||||
                    match status {
 | 
			
		||||
                        JobStatus::Finished => {
 | 
			
		||||
                            if let Ok(Some(result)) = self.dispatcher.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.dispatcher.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.dispatcher.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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
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!();
 | 
			
		||||
 | 
			
		||||
    // 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);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // 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