refactor wip
This commit is contained in:
197
core/worker/examples/README.md
Normal file
197
core/worker/examples/README.md
Normal 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.
|
11
core/worker/examples/osis/config.toml
Normal file
11
core/worker/examples/osis/config.toml
Normal 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"
|
138
core/worker/examples/osis/example.sh
Executable file
138
core/worker/examples/osis/example.sh
Executable 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
|
14
core/worker/examples/osis_config.toml
Normal file
14
core/worker/examples/osis_config.toml
Normal 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"
|
60
core/worker/examples/osis_worker_demo.rs
Normal file
60
core/worker/examples/osis_worker_demo.rs
Normal 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(¤t_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(())
|
||||
}
|
12
core/worker/examples/system/config.toml
Normal file
12
core/worker/examples/system/config.toml
Normal 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"
|
183
core/worker/examples/system/example.sh
Executable file
183
core/worker/examples/system/example.sh
Executable 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
|
15
core/worker/examples/system_config.toml
Normal file
15
core/worker/examples/system_config.toml
Normal 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"
|
60
core/worker/examples/system_worker_demo.rs
Normal file
60
core/worker/examples/system_worker_demo.rs
Normal 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(¤t_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(())
|
||||
}
|
322
core/worker/examples/trait_based_worker_demo.rs
Normal file
322
core/worker/examples/trait_based_worker_demo.rs
Normal 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(())
|
||||
}
|
Reference in New Issue
Block a user