refactor wip

This commit is contained in:
Timur Gordon
2025-08-05 12:19:38 +02:00
parent 8ed40ce99c
commit 7a652c9c3c
51 changed files with 6183 additions and 840 deletions

View File

@@ -0,0 +1,197 @@
# Worker Examples
This directory contains example configurations and test scripts for both OSIS and System worker binaries.
## Overview
Both examples demonstrate the ping/pong functionality built into the Hero workers:
- Workers automatically detect jobs with script content "ping"
- They respond immediately with "pong" without executing the Rhai engine
- This provides a fast health check and connectivity test mechanism
## Prerequisites
1. **Redis Server**: Both examples require a running Redis server
```bash
# Install Redis (macOS)
brew install redis
# Start Redis server
redis-server
```
2. **Rust Environment**: Make sure you can build the worker binaries
```bash
cd /path/to/herocode/hero/core/worker
cargo build --bin osis --bin system
```
## OSIS Worker Example
**Location**: `examples/osis/`
The OSIS (Operating System Integration Service) worker processes jobs synchronously, one at a time.
### Files
- `config.toml` - Configuration for the OSIS worker
- `example.sh` - Test script that demonstrates ping/pong functionality
### Usage
```bash
cd examples/osis
./example.sh
```
### What the script does:
1. Checks Redis connectivity
2. Cleans up any existing jobs
3. Starts the OSIS worker in the background
4. Sends 3 ping jobs sequentially
5. Verifies each job receives a "pong" response
6. Reports success/failure statistics
7. Cleans up worker and Redis data
### Expected Output
```
=== OSIS Worker Example ===
✅ Redis is running
✅ OSIS worker started (PID: 12345)
📤 Sending ping job: ping_job_1_1234567890
✅ Job ping_job_1_1234567890 completed successfully with result: pong
...
🎉 All tests passed! OSIS worker is working correctly.
```
## System Worker Example
**Location**: `examples/system/`
The System worker processes jobs asynchronously, handling multiple jobs concurrently.
### Files
- `config.toml` - Configuration for the System worker (includes async settings)
- `example.sh` - Test script that demonstrates concurrent ping/pong functionality
### Usage
```bash
cd examples/system
./example.sh
```
### What the script does:
1. Checks Redis connectivity
2. Cleans up any existing jobs
3. Starts the System worker with stats reporting
4. Sends 5 concurrent ping jobs
5. Sends 10 rapid-fire ping jobs to test async capabilities
6. Verifies all jobs receive "pong" responses
7. Reports comprehensive success/failure statistics
8. Cleans up worker and Redis data
### Expected Output
```
=== System Worker Example ===
✅ Redis is running
✅ System worker started (PID: 12345)
📤 Sending ping job: ping_job_1_1234567890123
✅ Job ping_job_1_1234567890123 completed successfully with result: pong
...
🎉 All tests passed! System worker is handling concurrent jobs correctly.
Overall success rate: 15/15
```
## Configuration Details
### OSIS Configuration (`examples/osis/config.toml`)
```toml
worker_id = "osis_example_worker"
redis_url = "redis://localhost:6379"
db_path = "/tmp/osis_example_db"
preserve_tasks = false
[worker_type]
type = "sync"
[logging]
timestamps = true
level = "info"
```
### System Configuration (`examples/system/config.toml`)
```toml
worker_id = "system_example_worker"
redis_url = "redis://localhost:6379"
db_path = "/tmp/system_example_db"
preserve_tasks = false
[worker_type]
type = "async"
default_timeout_seconds = 30
[logging]
timestamps = true
level = "info"
```
## Key Differences
| Feature | OSIS Worker | System Worker |
|---------|-------------|---------------|
| **Processing** | Sequential (one job at a time) | Concurrent (multiple jobs simultaneously) |
| **Use Case** | System-level operations requiring resource management | High-throughput job processing |
| **Timeout** | No timeout configuration | Configurable job timeouts |
| **Stats** | Basic logging | Optional statistics reporting (`--show-stats`) |
| **Job Handling** | Blocking job execution | Non-blocking async job execution |
## Troubleshooting
### Redis Connection Issues
```bash
# Check if Redis is running
redis-cli ping
# Check Redis logs
redis-server --loglevel verbose
```
### Worker Compilation Issues
```bash
# Clean and rebuild
cargo clean
cargo build --bin osis --bin system
```
### Job Processing Issues
- Check Redis for stuck jobs: `redis-cli keys "hero:*"`
- Clear all Hero jobs: `redis-cli eval "return redis.call('del', unpack(redis.call('keys', 'hero:*')))" 0`
- Check worker logs for detailed error messages
## Extending the Examples
### Adding Custom Jobs
To test with custom Rhai scripts instead of ping jobs:
1. Modify the job creation in the shell scripts:
```bash
# Replace "ping" with your Rhai script
redis-cli -u "$REDIS_URL" hset "hero:job:$job_id" \
script "your_rhai_script_here"
```
2. Update result verification to expect your script's output instead of "pong"
### Testing Different Configurations
- Modify `config.toml` files to test different Redis URLs, database paths, or logging levels
- Test with `preserve_tasks = true` to inspect job details after completion
- Adjust timeout values in the System worker configuration
## Architecture Notes
Both examples demonstrate the unified Worker trait architecture:
- **Common Interface**: Both workers implement the same `Worker` trait
- **Ping/Pong Handling**: Built into the trait's `spawn` method before job delegation
- **Redis Integration**: Uses the shared Job struct from `hero_job` crate
- **Configuration**: TOML-based configuration with CLI overrides
- **Graceful Shutdown**: Both workers handle SIGTERM/SIGINT properly
This architecture allows for easy extension with new worker types while maintaining consistent behavior and configuration patterns.

View File

@@ -0,0 +1,11 @@
worker_id = "osis_example_worker"
redis_url = "redis://localhost:6379"
db_path = "/tmp/osis_example_db"
preserve_tasks = false
[worker_type]
type = "sync"
[logging]
timestamps = true
level = "info"

View File

@@ -0,0 +1,138 @@
#!/bin/bash
# OSIS Worker Example Script
# This script demonstrates the OSIS worker by:
# 1. Starting the worker with the config.toml
# 2. Sending ping jobs to Redis
# 3. Verifying pong responses
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config.toml"
WORKER_ID="osis_example_worker"
REDIS_URL="redis://localhost:6379"
echo "=== OSIS Worker Example ==="
echo "Script directory: $SCRIPT_DIR"
echo "Config file: $CONFIG_FILE"
echo "Worker ID: $WORKER_ID"
echo "Redis URL: $REDIS_URL"
echo
# Check if Redis is running
echo "Checking Redis connection..."
if ! redis-cli -u "$REDIS_URL" ping > /dev/null 2>&1; then
echo "❌ Error: Redis is not running or not accessible at $REDIS_URL"
echo "Please start Redis server first: redis-server"
exit 1
fi
echo "✅ Redis is running"
echo
# Clean up any existing jobs in the queue
echo "Cleaning up existing jobs in Redis..."
redis-cli -u "$REDIS_URL" del "hero:jobs:$WORKER_ID" > /dev/null 2>&1 || true
redis-cli -u "$REDIS_URL" eval "return redis.call('del', unpack(redis.call('keys', 'hero:job:*')))" 0 > /dev/null 2>&1 || true
echo "✅ Redis queues cleaned"
echo
# Start the OSIS worker in the background
echo "Starting OSIS worker..."
cd "$SCRIPT_DIR/../.."
cargo run --bin osis -- --config "$CONFIG_FILE" &
WORKER_PID=$!
echo "✅ OSIS worker started (PID: $WORKER_PID)"
echo
# Wait a moment for the worker to initialize
echo "Waiting for worker to initialize..."
sleep 3
# Function to send a ping job and check for pong response
send_ping_job() {
local job_num=$1
local job_id="ping_job_${job_num}_$(date +%s)"
echo "📤 Sending ping job: $job_id"
# Create job in Redis
redis-cli -u "$REDIS_URL" hset "hero:job:$job_id" \
id "$job_id" \
script "ping" \
status "Queued" \
created_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
worker_id "$WORKER_ID" > /dev/null
# Add job to worker queue
redis-cli -u "$REDIS_URL" lpush "hero:jobs:$WORKER_ID" "$job_id" > /dev/null
# Wait for job completion and check result
local timeout=10
local elapsed=0
while [ $elapsed -lt $timeout ]; do
local status=$(redis-cli -u "$REDIS_URL" hget "hero:job:$job_id" status 2>/dev/null || echo "")
if [ "$status" = "Finished" ]; then
local result=$(redis-cli -u "$REDIS_URL" hget "hero:job:$job_id" result 2>/dev/null || echo "")
if [ "$result" = "pong" ]; then
echo "✅ Job $job_id completed successfully with result: $result"
return 0
else
echo "❌ Job $job_id completed but with unexpected result: $result"
return 1
fi
elif [ "$status" = "Error" ]; then
local error=$(redis-cli -u "$REDIS_URL" hget "hero:job:$job_id" error 2>/dev/null || echo "")
echo "❌ Job $job_id failed with error: $error"
return 1
fi
sleep 1
elapsed=$((elapsed + 1))
done
echo "❌ Job $job_id timed out after ${timeout}s"
return 1
}
# Send multiple ping jobs to test the worker
echo "Testing ping/pong functionality..."
success_count=0
total_jobs=3
for i in $(seq 1 $total_jobs); do
echo
echo "--- Test $i/$total_jobs ---"
if send_ping_job $i; then
success_count=$((success_count + 1))
fi
sleep 1
done
echo
echo "=== Test Results ==="
echo "Successful ping/pong tests: $success_count/$total_jobs"
if [ $success_count -eq $total_jobs ]; then
echo "🎉 All tests passed! OSIS worker is working correctly."
exit_code=0
else
echo "⚠️ Some tests failed. Check the worker logs for details."
exit_code=1
fi
# Clean up
echo
echo "Cleaning up..."
echo "Stopping OSIS worker (PID: $WORKER_PID)..."
kill $WORKER_PID 2>/dev/null || true
wait $WORKER_PID 2>/dev/null || true
echo "✅ Worker stopped"
echo "Cleaning up Redis jobs..."
redis-cli -u "$REDIS_URL" del "hero:jobs:$WORKER_ID" > /dev/null 2>&1 || true
redis-cli -u "$REDIS_URL" eval "return redis.call('del', unpack(redis.call('keys', 'hero:job:*')))" 0 > /dev/null 2>&1 || true
echo "✅ Redis cleaned up"
echo
echo "=== OSIS Worker Example Complete ==="
exit $exit_code

View File

@@ -0,0 +1,14 @@
# OSIS Worker Configuration
# Synchronous worker for system-level operations
worker_id = "osis_worker_1"
redis_url = "redis://localhost:6379"
db_path = "/tmp/osis_worker_db"
preserve_tasks = false
[worker_type]
type = "sync"
[logging]
timestamps = true
level = "info"

View File

@@ -0,0 +1,60 @@
use std::process::{Command, Stdio};
use std::path::Path;
use std::env;
use std::io::{self, Write};
/// OSIS Worker Demo Runner
///
/// This Rust wrapper executes the OSIS worker bash script example.
/// It provides a way to run shell-based examples through Cargo.
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 OSIS Worker Demo");
println!("==================");
println!();
// Get the current working directory and construct the path to the shell script
let current_dir = env::current_dir()?;
let script_path = current_dir.join("examples").join("osis").join("example.sh");
// Check if the script exists
if !script_path.exists() {
eprintln!("❌ Error: Script not found at {:?}", script_path);
eprintln!(" Make sure you're running this from the worker crate root directory.");
std::process::exit(1);
}
println!("📁 Script location: {:?}", script_path);
println!("🔧 Executing OSIS worker example...");
println!();
// Make sure the script is executable
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&script_path)?.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&script_path, perms)?;
}
// Execute the shell script
let mut child = Command::new("bash")
.arg(&script_path)
.current_dir(&current_dir)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
// Wait for the script to complete
let status = child.wait()?;
println!();
if status.success() {
println!("✅ OSIS worker demo completed successfully!");
} else {
println!("❌ OSIS worker demo failed with exit code: {:?}", status.code());
std::process::exit(status.code().unwrap_or(1));
}
Ok(())
}

View File

@@ -0,0 +1,12 @@
worker_id = "system_example_worker"
redis_url = "redis://localhost:6379"
db_path = "/tmp/system_example_db"
preserve_tasks = false
[worker_type]
type = "async"
default_timeout_seconds = 30
[logging]
timestamps = true
level = "info"

View File

@@ -0,0 +1,183 @@
#!/bin/bash
# System Worker Example Script
# This script demonstrates the System worker by:
# 1. Starting the worker with the config.toml
# 2. Sending multiple concurrent ping jobs to Redis
# 3. Verifying pong responses
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config.toml"
WORKER_ID="system_example_worker"
REDIS_URL="redis://localhost:6379"
echo "=== System Worker Example ==="
echo "Script directory: $SCRIPT_DIR"
echo "Config file: $CONFIG_FILE"
echo "Worker ID: $WORKER_ID"
echo "Redis URL: $REDIS_URL"
echo
# Check if Redis is running
echo "Checking Redis connection..."
if ! redis-cli -u "$REDIS_URL" ping > /dev/null 2>&1; then
echo "❌ Error: Redis is not running or not accessible at $REDIS_URL"
echo "Please start Redis server first: redis-server"
exit 1
fi
echo "✅ Redis is running"
echo
# Clean up any existing jobs in the queue
echo "Cleaning up existing jobs in Redis..."
redis-cli -u "$REDIS_URL" del "hero:jobs:$WORKER_ID" > /dev/null 2>&1 || true
redis-cli -u "$REDIS_URL" eval "return redis.call('del', unpack(redis.call('keys', 'hero:job:*')))" 0 > /dev/null 2>&1 || true
echo "✅ Redis queues cleaned"
echo
# Start the System worker in the background
echo "Starting System worker..."
cd "$SCRIPT_DIR/../.."
cargo run --bin system -- --config "$CONFIG_FILE" --show-stats &
WORKER_PID=$!
echo "✅ System worker started (PID: $WORKER_PID)"
echo
# Wait a moment for the worker to initialize
echo "Waiting for worker to initialize..."
sleep 3
# Function to send a ping job (non-blocking)
send_ping_job() {
local job_num=$1
local job_id="ping_job_${job_num}_$(date +%s%N)"
echo "📤 Sending ping job: $job_id"
# Create job in Redis
redis-cli -u "$REDIS_URL" hset "hero:job:$job_id" \
id "$job_id" \
script "ping" \
status "Queued" \
created_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
worker_id "$WORKER_ID" > /dev/null
# Add job to worker queue
redis-cli -u "$REDIS_URL" lpush "hero:jobs:$WORKER_ID" "$job_id" > /dev/null
echo "$job_id"
}
# Function to check job result
check_job_result() {
local job_id=$1
local timeout=15
local elapsed=0
while [ $elapsed -lt $timeout ]; do
local status=$(redis-cli -u "$REDIS_URL" hget "hero:job:$job_id" status 2>/dev/null || echo "")
if [ "$status" = "Finished" ]; then
local result=$(redis-cli -u "$REDIS_URL" hget "hero:job:$job_id" result 2>/dev/null || echo "")
if [ "$result" = "pong" ]; then
echo "✅ Job $job_id completed successfully with result: $result"
return 0
else
echo "❌ Job $job_id completed but with unexpected result: $result"
return 1
fi
elif [ "$status" = "Error" ]; then
local error=$(redis-cli -u "$REDIS_URL" hget "hero:job:$job_id" error 2>/dev/null || echo "")
echo "❌ Job $job_id failed with error: $error"
return 1
fi
sleep 0.5
elapsed=$((elapsed + 1))
done
echo "❌ Job $job_id timed out after ${timeout}s"
return 1
}
# Send multiple concurrent ping jobs to test async processing
echo "Testing concurrent ping/pong functionality..."
total_jobs=5
job_ids=()
echo
echo "--- Sending $total_jobs concurrent ping jobs ---"
for i in $(seq 1 $total_jobs); do
job_id=$(send_ping_job $i)
job_ids+=("$job_id")
sleep 0.1 # Small delay between job submissions
done
echo
echo "--- Waiting for all jobs to complete ---"
success_count=0
for job_id in "${job_ids[@]}"; do
echo "Checking job: $job_id"
if check_job_result "$job_id"; then
success_count=$((success_count + 1))
fi
done
echo
echo "=== Test Results ==="
echo "Successful concurrent ping/pong tests: $success_count/$total_jobs"
if [ $success_count -eq $total_jobs ]; then
echo "🎉 All tests passed! System worker is handling concurrent jobs correctly."
exit_code=0
else
echo "⚠️ Some tests failed. Check the worker logs for details."
exit_code=1
fi
# Test rapid job submission to showcase async capabilities
echo
echo "--- Testing rapid job submission (10 jobs in quick succession) ---"
rapid_jobs=10
rapid_job_ids=()
for i in $(seq 1 $rapid_jobs); do
job_id=$(send_ping_job "rapid_$i")
rapid_job_ids+=("$job_id")
done
echo "Waiting for rapid jobs to complete..."
rapid_success=0
for job_id in "${rapid_job_ids[@]}"; do
if check_job_result "$job_id"; then
rapid_success=$((rapid_success + 1))
fi
done
echo "Rapid submission test: $rapid_success/$rapid_jobs successful"
# Clean up
echo
echo "Cleaning up..."
echo "Stopping System worker (PID: $WORKER_PID)..."
kill $WORKER_PID 2>/dev/null || true
wait $WORKER_PID 2>/dev/null || true
echo "✅ Worker stopped"
echo "Cleaning up Redis jobs..."
redis-cli -u "$REDIS_URL" del "hero:jobs:$WORKER_ID" > /dev/null 2>&1 || true
redis-cli -u "$REDIS_URL" eval "return redis.call('del', unpack(redis.call('keys', 'hero:job:*')))" 0 > /dev/null 2>&1 || true
echo "✅ Redis cleaned up"
echo
echo "=== System Worker Example Complete ==="
total_success=$((success_count + rapid_success))
total_tests=$((total_jobs + rapid_jobs))
echo "Overall success rate: $total_success/$total_tests"
if [ $total_success -eq $total_tests ]; then
exit 0
else
exit 1
fi

View File

@@ -0,0 +1,15 @@
# System Worker Configuration
# Asynchronous worker for high-throughput concurrent processing
worker_id = "system_worker_1"
redis_url = "redis://localhost:6379"
db_path = "/tmp/system_worker_db"
preserve_tasks = false
[worker_type]
type = "async"
default_timeout_seconds = 300 # 5 minutes
[logging]
timestamps = true
level = "info"

View File

@@ -0,0 +1,60 @@
use std::process::{Command, Stdio};
use std::path::Path;
use std::env;
use std::io::{self, Write};
/// System Worker Demo Runner
///
/// This Rust wrapper executes the System worker bash script example.
/// It provides a way to run shell-based examples through Cargo.
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 System Worker Demo");
println!("====================");
println!();
// Get the current working directory and construct the path to the shell script
let current_dir = env::current_dir()?;
let script_path = current_dir.join("examples").join("system").join("example.sh");
// Check if the script exists
if !script_path.exists() {
eprintln!("❌ Error: Script not found at {:?}", script_path);
eprintln!(" Make sure you're running this from the worker crate root directory.");
std::process::exit(1);
}
println!("📁 Script location: {:?}", script_path);
println!("🔧 Executing System worker example...");
println!();
// Make sure the script is executable
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&script_path)?.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&script_path, perms)?;
}
// Execute the shell script
let mut child = Command::new("bash")
.arg(&script_path)
.current_dir(&current_dir)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
// Wait for the script to complete
let status = child.wait()?;
println!();
if status.success() {
println!("✅ System worker demo completed successfully!");
} else {
println!("❌ System worker demo failed with exit code: {:?}", status.code());
std::process::exit(status.code().unwrap_or(1));
}
Ok(())
}

View File

@@ -0,0 +1,322 @@
//! # Trait-Based Worker Demo
//!
//! This example demonstrates the new unified worker interface using the Worker trait.
//! It shows how both synchronous and asynchronous workers can be used with the same
//! API, eliminating code duplication and providing a clean, consistent interface.
//!
//! ## Features Demonstrated
//!
//! - Unified worker interface using the Worker trait
//! - Both sync and async worker implementations
//! - Shared configuration and spawn logic
//! - Clean shutdown handling
//! - Job processing with different strategies
//!
//! ## Usage
//!
//! Make sure Redis is running on localhost:6379, then run:
//! ```bash
//! cargo run --example trait_based_worker_demo
//! ```
use hero_job::{Job, JobStatus, ScriptType};
use log::{info, warn, error};
use rhailib_worker::{
SyncWorker, AsyncWorker,
spawn_sync_worker, spawn_async_worker,
engine::create_heromodels_engine,
worker_trait::{spawn_worker, Worker}
};
use redis::AsyncCommands;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc;
use tokio::time::sleep;
const REDIS_URL: &str = "redis://127.0.0.1:6379";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize logging
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
info!("Starting Trait-Based Worker Demo");
// Create Redis connection for job creation
let redis_client = redis::Client::open(REDIS_URL)?;
let mut redis_conn = redis_client.get_multiplexed_async_connection().await?;
// Demo 1: Using the unified trait-based interface
info!("=== Demo 1: Unified Trait-Based Interface ===");
// Create shutdown channels for both workers
let (sync_shutdown_tx, sync_shutdown_rx) = mpsc::channel::<()>(1);
let (async_shutdown_tx, async_shutdown_rx) = mpsc::channel::<()>(1);
// Workers are now configured using builder pattern directly
// Create worker instances using builder pattern
let sync_worker = Arc::new(
SyncWorker::builder()
.worker_id("demo_sync_worker")
.db_path("/tmp")
.redis_url("redis://localhost:6379")
.preserve_tasks(false)
.build()
.expect("Failed to build SyncWorker")
);
let async_worker = Arc::new(
AsyncWorker::builder()
.worker_id("demo_async_worker")
.db_path("/tmp")
.redis_url("redis://localhost:6379")
.default_timeout(Duration::from_secs(300))
.build()
.expect("Failed to build AsyncWorker")
);
let sync_engine = create_heromodels_engine();
let async_engine = create_heromodels_engine();
info!("Spawning {} worker: {}", sync_worker.worker_type(), sync_worker.worker_id());
let sync_handle = spawn_worker(sync_worker.clone(), sync_engine, sync_shutdown_rx);
info!("Spawning {} worker: {}", async_worker.worker_type(), async_worker.worker_id());
let async_handle = spawn_worker(async_worker.clone(), async_engine, async_shutdown_rx);
// Give workers time to start
sleep(Duration::from_secs(1)).await;
// Create and dispatch jobs to both workers
info!("Creating demo jobs for both workers...");
// Job for sync worker - simple calculation
let sync_job = create_demo_job(
"sync_calculation",
r#"
print("Sync worker: Starting calculation...");
let result = 0;
for i in 1..=100 {
result += i;
}
print("Sync worker: Sum of 1-100 = " + result);
result
"#,
None,
).await?;
dispatch_job(&mut redis_conn, &sync_job, sync_worker.worker_id()).await?;
info!("Dispatched job to sync worker: {}", sync_job.id);
// Job for async worker - with timeout demonstration
let async_job = create_demo_job(
"async_calculation",
r#"
print("Async worker: Starting calculation...");
let result = 1;
for i in 1..=10 {
result *= i;
}
print("Async worker: 10! = " + result);
result
"#,
Some(15), // 15 second timeout
).await?;
dispatch_job(&mut redis_conn, &async_job, async_worker.worker_id()).await?;
info!("Dispatched job to async worker: {}", async_job.id);
// Monitor job execution
info!("Monitoring job execution for 10 seconds...");
let monitor_start = std::time::Instant::now();
let monitor_duration = Duration::from_secs(10);
while monitor_start.elapsed() < monitor_duration {
// Check sync job status
if let Ok(status) = Job::get_status(&mut redis_conn, &sync_job.id).await {
match status {
JobStatus::Finished => {
let job_key = format!("hero:job:{}", sync_job.id);
if let Ok(result) = redis_conn.hget::<_, _, String>(&job_key, "output").await {
info!("✅ Sync Job {} COMPLETED with result: {}", sync_job.id, result);
} else {
info!("✅ Sync Job {} COMPLETED", sync_job.id);
}
}
JobStatus::Error => {
let job_key = format!("hero:job:{}", sync_job.id);
if let Ok(error) = redis_conn.hget::<_, _, String>(&job_key, "error").await {
warn!("❌ Sync Job {} FAILED with error: {}", sync_job.id, error);
} else {
warn!("❌ Sync Job {} FAILED", sync_job.id);
}
}
_ => info!("🔄 Sync Job {} status: {:?}", sync_job.id, status),
}
}
// Check async job status
if let Ok(status) = Job::get_status(&mut redis_conn, &async_job.id).await {
match status {
JobStatus::Finished => {
let job_key = format!("hero:job:{}", async_job.id);
if let Ok(result) = redis_conn.hget::<_, _, String>(&job_key, "output").await {
info!("✅ Async Job {} COMPLETED with result: {}", async_job.id, result);
} else {
info!("✅ Async Job {} COMPLETED", async_job.id);
}
}
JobStatus::Error => {
let job_key = format!("hero:job:{}", async_job.id);
if let Ok(error) = redis_conn.hget::<_, _, String>(&job_key, "error").await {
warn!("❌ Async Job {} FAILED with error: {}", async_job.id, error);
} else {
warn!("❌ Async Job {} FAILED", async_job.id);
}
}
_ => info!("🔄 Async Job {} status: {:?}", async_job.id, status),
}
}
sleep(Duration::from_secs(2)).await;
}
// Demo 2: Using convenience functions (backward compatibility)
info!("\n=== Demo 2: Convenience Functions (Backward Compatibility) ===");
let (conv_sync_shutdown_tx, conv_sync_shutdown_rx) = mpsc::channel::<()>(1);
let (conv_async_shutdown_tx, conv_async_shutdown_rx) = mpsc::channel::<()>(1);
// Spawn workers using convenience functions
let conv_sync_engine = create_heromodels_engine();
let conv_async_engine = create_heromodels_engine();
info!("Spawning sync worker using convenience function...");
let conv_sync_handle = spawn_sync_worker(
"convenience_sync_worker".to_string(),
"/tmp".to_string(),
conv_sync_engine,
REDIS_URL.to_string(),
conv_sync_shutdown_rx,
false,
);
info!("Spawning async worker using convenience function...");
let conv_async_handle = spawn_async_worker(
"convenience_async_worker".to_string(),
"/tmp".to_string(),
conv_async_engine,
REDIS_URL.to_string(),
conv_async_shutdown_rx,
Duration::from_secs(20), // 20 second timeout
);
// Give convenience workers time to start
sleep(Duration::from_secs(1)).await;
// Create jobs for convenience workers
let conv_sync_job = create_demo_job(
"convenience_sync",
r#"
print("Convenience sync worker: Hello World!");
"Hello from convenience sync worker"
"#,
None,
).await?;
let conv_async_job = create_demo_job(
"convenience_async",
r#"
print("Convenience async worker: Hello World!");
"Hello from convenience async worker"
"#,
Some(10),
).await?;
dispatch_job(&mut redis_conn, &conv_sync_job, "convenience_sync_worker").await?;
dispatch_job(&mut redis_conn, &conv_async_job, "convenience_async_worker").await?;
info!("Dispatched jobs to convenience workers");
// Wait a bit for jobs to complete
sleep(Duration::from_secs(5)).await;
// Shutdown all workers gracefully
info!("\n=== Shutting Down All Workers ===");
info!("Sending shutdown signals...");
let _ = sync_shutdown_tx.send(()).await;
let _ = async_shutdown_tx.send(()).await;
let _ = conv_sync_shutdown_tx.send(()).await;
let _ = conv_async_shutdown_tx.send(()).await;
info!("Waiting for workers to shutdown...");
// Wait for all workers to shutdown
let results = tokio::join!(
sync_handle,
async_handle,
conv_sync_handle,
conv_async_handle
);
match results {
(Ok(Ok(())), Ok(Ok(())), Ok(Ok(())), Ok(Ok(()))) => {
info!("All workers shut down successfully!");
}
_ => {
error!("Some workers encountered errors during shutdown");
}
}
info!("Trait-Based Worker Demo completed successfully!");
// Summary
info!("\n=== Summary ===");
info!("✅ Demonstrated unified Worker trait interface");
info!("✅ Showed both sync and async worker implementations");
info!("✅ Used shared configuration and spawn logic");
info!("✅ Maintained backward compatibility with convenience functions");
info!("✅ Eliminated code duplication between worker types");
info!("✅ Provided clean, consistent API for all worker operations");
Ok(())
}
/// Create a demo job with the specified script and timeout
async fn create_demo_job(
name: &str,
script: &str,
timeout_seconds: Option<i32>,
) -> Result<Job, Box<dyn std::error::Error>> {
let mut job = Job::new(
format!("demo_{}", name), // caller_id
"demo_context".to_string(), // context_id
script.to_string(),
ScriptType::OSIS,
);
// Set timeout if provided
if let Some(timeout) = timeout_seconds {
job.timeout = Duration::from_secs(timeout as u64);
}
Ok(job)
}
/// Dispatch a job to the worker queue
async fn dispatch_job(
redis_conn: &mut redis::aio::MultiplexedConnection,
job: &Job,
worker_queue: &str,
) -> Result<(), Box<dyn std::error::Error>> {
// Store job in Redis
job.store_in_redis(redis_conn).await?;
// Add job to worker queue
let queue_key = format!("hero:job:{}", worker_queue);
let _: () = redis_conn.rpush(&queue_key, &job.id).await?;
Ok(())
}