rename worker to actor

This commit is contained in:
Timur Gordon
2025-08-05 15:44:33 +02:00
parent 5283f383b3
commit 89e953ca1d
67 changed files with 1629 additions and 1737 deletions

View File

@@ -1,20 +1,20 @@
# Minimal Rhailib Benchmark
# Minimal baobab Benchmark
A simplified, minimal benchmarking tool for rhailib performance testing.
A simplified, minimal benchmarking tool for baobab performance testing.
## Overview
This benchmark focuses on simplicity and direct timing measurements:
- Creates a single task (n=1) using Lua script
- Measures latency using Redis timestamps
- Uses existing worker binary
- Uses existing actor binary
- ~85 lines of code total
## Usage
### Prerequisites
- Redis running on `127.0.0.1:6379`
- Worker binary built: `cd src/worker && cargo build --release`
- Actor binary built: `cd src/actor && cargo build --release`
### Run Benchmark
```bash
@@ -25,7 +25,7 @@ cargo bench
### Expected Output
```
🧹 Cleaning up Redis...
🚀 Starting worker...
🚀 Starting actor...
📝 Creating single task...
⏱️ Waiting for completion...
✅ Task completed in 23.45ms
@@ -42,10 +42,10 @@ cargo bench
## How It Works
1. **Cleanup**: Clear Redis queues and task details
2. **Start Worker**: Spawn single worker process
2. **Start Actor**: Spawn single actor process
3. **Create Task**: Use Lua script to create one task with timestamp
4. **Wait & Measure**: Poll task until complete, calculate latency
5. **Cleanup**: Kill worker and clear Redis
5. **Cleanup**: Kill actor and clear Redis
## Latency Calculation
@@ -55,7 +55,7 @@ latency_ms = updated_at - created_at
Where:
- `created_at`: Timestamp when task was created (Lua script)
- `updated_at`: Timestamp when worker completed task
- `updated_at`: Timestamp when actor completed task
## Future Iterations

View File

@@ -15,7 +15,7 @@ if task_count <= 0 or task_count > 10000 then
return redis.error_reply("task_count must be a positive integer between 1 and 10000")
end
-- Get current timestamp in Unix seconds (to match worker expectations)
-- Get current timestamp in Unix seconds (to match actor expectations)
local rhai_task_queue = 'rhai_tasks:' .. circle_name
local task_keys = {}
local current_time = redis.call('TIME')[1]
@@ -35,7 +35,7 @@ for i = 1, task_count do
'task_sequence', tostring(i)
)
-- Queue the task for workers
-- Queue the task for actors
redis.call('LPUSH', rhai_task_queue, task_id)
-- Add key to return array

View File

@@ -23,23 +23,23 @@ fn cleanup_redis() -> Result<(), redis::RedisError> {
Ok(())
}
fn start_worker() -> Result<Child, std::io::Error> {
fn start_actor() -> Result<Child, std::io::Error> {
Command::new("cargo")
.args(&[
"run",
"--release",
"--bin",
"worker",
"actor",
"--",
"--circle",
CIRCLE_NAME,
"--redis-url",
REDIS_URL,
"--worker-id",
"bench_worker",
"--actor-id",
"bench_actor",
"--preserve-tasks",
])
.current_dir("src/worker")
.current_dir("src/actor")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
@@ -126,26 +126,26 @@ fn wait_for_batch_completion(task_keys: &[String]) -> Result<f64, Box<dyn std::e
}
}
fn cleanup_worker(mut worker: Child) -> Result<(), std::io::Error> {
worker.kill()?;
worker.wait()?;
fn cleanup_actor(mut actor: Child) -> Result<(), std::io::Error> {
actor.kill()?;
actor.wait()?;
Ok(())
}
fn bench_single_rhai_task(c: &mut Criterion) {
// Setup: ensure worker is built
// Setup: ensure actor is built
let _ = Command::new("cargo")
.args(&["build", "--release", "--bin", "worker"])
.current_dir("src/worker")
.args(&["build", "--release", "--bin", "actor"])
.current_dir("src/actor")
.output()
.expect("Failed to build worker");
.expect("Failed to build actor");
// Clean up before starting
cleanup_redis().expect("Failed to cleanup Redis");
// Start worker once and reuse it
let worker = start_worker().expect("Failed to start worker");
thread::sleep(Duration::from_millis(1000)); // Give worker time to start
// Start actor once and reuse it
let actor = start_actor().expect("Failed to start actor");
thread::sleep(Duration::from_millis(1000)); // Give actor time to start
let mut group = c.benchmark_group("rhai_task_execution");
group.sample_size(10); // Reduce sample size
@@ -174,8 +174,8 @@ fn bench_single_rhai_task(c: &mut Criterion) {
group.finish();
// Cleanup worker
cleanup_worker(worker).expect("Failed to cleanup worker");
// Cleanup actor
cleanup_actor(actor).expect("Failed to cleanup actor");
cleanup_redis().expect("Failed to cleanup Redis");
}

View File

@@ -1,38 +1,38 @@
# Rhai Worker Binary
# Rhai Actor Binary
A command-line worker for executing Rhai scripts from Redis task queues.
A command-line actor for executing Rhai scripts from Redis task queues.
## Binary: `worker`
## Binary: `actor`
### Installation
Build the binary:
```bash
cargo build --bin worker --release
cargo build --bin actor --release
```
### Usage
```bash
# Basic usage - requires circle public key
worker --circle-public-key <CIRCLE_PUBLIC_KEY>
actor --circle-public-key <CIRCLE_PUBLIC_KEY>
# Custom Redis URL
worker -c <CIRCLE_PUBLIC_KEY> --redis-url redis://localhost:6379/1
actor -c <CIRCLE_PUBLIC_KEY> --redis-url redis://localhost:6379/1
# Custom worker ID and database path
worker -c <CIRCLE_PUBLIC_KEY> --worker-id my_worker --db-path /tmp/worker_db
# Custom actor ID and database path
actor -c <CIRCLE_PUBLIC_KEY> --actor-id my_actor --db-path /tmp/actor_db
# Preserve tasks for debugging/benchmarking
worker -c <CIRCLE_PUBLIC_KEY> --preserve-tasks
actor -c <CIRCLE_PUBLIC_KEY> --preserve-tasks
# Remove timestamps from logs
worker -c <CIRCLE_PUBLIC_KEY> --no-timestamp
actor -c <CIRCLE_PUBLIC_KEY> --no-timestamp
# Increase verbosity
worker -c <CIRCLE_PUBLIC_KEY> -v # Debug logging
worker -c <CIRCLE_PUBLIC_KEY> -vv # Full debug
worker -c <CIRCLE_PUBLIC_KEY> -vvv # Trace logging
actor -c <CIRCLE_PUBLIC_KEY> -v # Debug logging
actor -c <CIRCLE_PUBLIC_KEY> -vv # Full debug
actor -c <CIRCLE_PUBLIC_KEY> -vvv # Trace logging
```
### Command-Line Options
@@ -41,9 +41,9 @@ worker -c <CIRCLE_PUBLIC_KEY> -vvv # Trace logging
|--------|-------|---------|-------------|
| `--circle-public-key` | `-c` | **Required** | Circle public key to listen for tasks |
| `--redis-url` | `-r` | `redis://localhost:6379` | Redis connection URL |
| `--worker-id` | `-w` | `worker_1` | Unique worker identifier |
| `--actor-id` | `-w` | `actor_1` | Unique actor identifier |
| `--preserve-tasks` | | `false` | Preserve task details after completion |
| `--db-path` | | `worker_rhai_temp_db` | Database path for Rhai engine |
| `--db-path` | | `actor_rhai_temp_db` | Database path for Rhai engine |
| `--no-timestamp` | | `false` | Remove timestamps from log output |
| `--verbose` | `-v` | | Increase verbosity (stackable) |
@@ -58,7 +58,7 @@ worker -c <CIRCLE_PUBLIC_KEY> -vvv # Trace logging
### How It Works
1. **Queue Listening**: Worker listens on Redis queue `rhailib:{circle_public_key}`
1. **Queue Listening**: Actor listens on Redis queue `baobab:{circle_public_key}`
2. **Task Processing**: Receives task IDs, fetches task details from Redis
3. **Script Execution**: Executes Rhai scripts with configured engine
4. **Result Handling**: Updates task status and sends results to reply queues
@@ -66,30 +66,30 @@ worker -c <CIRCLE_PUBLIC_KEY> -vvv # Trace logging
### Configuration Examples
#### Development Worker
#### Development Actor
```bash
# Simple development worker
worker -c dev_circle_123
# Simple development actor
actor -c dev_circle_123
# Development with verbose logging (no timestamps)
worker -c dev_circle_123 -v --no-timestamp
actor -c dev_circle_123 -v --no-timestamp
```
#### Production Worker
#### Production Actor
```bash
# Production worker with custom configuration
worker \
# Production actor with custom configuration
actor \
--circle-public-key prod_circle_456 \
--redis-url redis://redis-server:6379/0 \
--worker-id prod_worker_1 \
--db-path /var/lib/worker/db \
--actor-id prod_actor_1 \
--db-path /var/lib/actor/db \
--preserve-tasks
```
#### Benchmarking Worker
#### Benchmarking Actor
```bash
# Worker optimized for benchmarking
worker \
# Actor optimized for benchmarking
actor \
--circle-public-key bench_circle_789 \
--preserve-tasks \
--no-timestamp \
@@ -98,7 +98,7 @@ worker \
### Error Handling
The worker provides clear error messages for:
The actor provides clear error messages for:
- Missing or invalid circle public key
- Redis connection failures
- Script execution errors
@@ -106,7 +106,7 @@ The worker provides clear error messages for:
### Dependencies
- `rhailib_engine`: Rhai engine with heromodels integration
- `baobab_engine`: Rhai engine with heromodels integration
- `redis`: Redis client for task queue management
- `rhai`: Script execution engine
- `clap`: Command-line argument parsing

View File

@@ -1,11 +1,11 @@
//! OSIS Worker Binary - Synchronous worker for system-level operations
//! OSIS Actor Binary - Synchronous actor for system-level operations
use clap::Parser;
use log::{error, info};
use rhailib_worker::config::{ConfigError, WorkerConfig};
use rhailib_worker::engine::create_heromodels_engine;
use rhailib_worker::sync_worker::SyncWorker;
use rhailib_worker::worker_trait::{spawn_worker, WorkerConfig as TraitWorkerConfig};
use baobab_actor::config::{ConfigError, ActorConfig};
use baobab_actor::engine::create_heromodels_engine;
use baobab_actor::sync_actor::SyncActor;
use baobab_actor::actor_trait::{spawn_actor, ActorConfig as TraitActorConfig};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::signal;
@@ -15,8 +15,8 @@ use tokio::sync::mpsc;
#[command(
name = "osis",
version = "0.1.0",
about = "OSIS (Operating System Integration Service) - Synchronous Worker",
long_about = "A synchronous worker for Hero framework that processes jobs sequentially. \
about = "OSIS (Operating System Integration Service) - Synchronous Actor",
long_about = "A synchronous actor for Hero framework that processes jobs sequentially. \
Ideal for system-level operations that require careful resource management."
)]
struct Args {
@@ -24,9 +24,9 @@ struct Args {
#[arg(short, long, help = "Path to TOML configuration file")]
config: PathBuf,
/// Override worker ID from config
#[arg(long, help = "Override worker ID from configuration file")]
worker_id: Option<String>,
/// Override actor ID from config
#[arg(long, help = "Override actor ID from configuration file")]
actor_id: Option<String>,
/// Override Redis URL from config
#[arg(long, help = "Override Redis URL from configuration file")]
@@ -50,7 +50,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let args = Args::parse();
// Load configuration from TOML file
let mut config = match WorkerConfig::from_file(&args.config) {
let mut config = match ActorConfig::from_file(&args.config) {
Ok(config) => config,
Err(e) => {
eprintln!("Failed to load configuration from {:?}: {}", args.config, e);
@@ -58,17 +58,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
};
// Validate that this is a sync worker configuration
// Validate that this is a sync actor configuration
if !config.is_sync() {
eprintln!("Error: OSIS worker requires a sync worker configuration");
eprintln!("Expected: [worker_type] type = \"sync\"");
eprintln!("Found: {:?}", config.worker_type);
eprintln!("Error: OSIS actor requires a sync actor configuration");
eprintln!("Expected: [actor_type] type = \"sync\"");
eprintln!("Found: {:?}", config.actor_type);
std::process::exit(1);
}
// Apply command line overrides
if let Some(worker_id) = args.worker_id {
config.worker_id = worker_id;
if let Some(actor_id) = args.actor_id {
config.actor_id = actor_id;
}
if let Some(redis_url) = args.redis_url {
config.redis_url = redis_url;
@@ -80,8 +80,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Configure logging
setup_logging(&config, args.verbose, args.no_timestamp)?;
info!("🚀 OSIS Worker starting...");
info!("Worker ID: {}", config.worker_id);
info!("🚀 OSIS Actor starting...");
info!("Actor ID: {}", config.actor_id);
info!("Redis URL: {}", config.redis_url);
info!("Database Path: {}", config.db_path);
info!("Preserve Tasks: {}", config.preserve_tasks);
@@ -90,17 +90,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let engine = create_heromodels_engine();
info!("✅ Rhai engine initialized");
// Create worker configuration for the trait-based interface
let worker_config = TraitWorkerConfig::new(
config.worker_id.clone(),
// Create actor configuration for the trait-based interface
let actor_config = TraitActorConfig::new(
config.actor_id.clone(),
config.db_path.clone(),
config.redis_url.clone(),
config.preserve_tasks,
);
// Create sync worker instance
let worker = Arc::new(SyncWorker::default());
info!("✅ Sync worker instance created");
// Create sync actor instance
let actor = Arc::new(SyncActor::default());
info!("✅ Sync actor instance created");
// Setup shutdown signal handling
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
@@ -118,21 +118,21 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
});
// Spawn the worker
info!("🔄 Starting worker loop...");
let worker_handle = spawn_worker(worker, engine, shutdown_rx);
// Spawn the actor
info!("🔄 Starting actor loop...");
let actor_handle = spawn_actor(actor, engine, shutdown_rx);
// Wait for the worker to complete
match worker_handle.await {
// Wait for the actor to complete
match actor_handle.await {
Ok(Ok(())) => {
info!("✅ OSIS Worker shut down gracefully");
info!("✅ OSIS Actor shut down gracefully");
}
Ok(Err(e)) => {
error!("❌ OSIS Worker encountered an error: {}", e);
error!("❌ OSIS Actor encountered an error: {}", e);
std::process::exit(1);
}
Err(e) => {
error!("❌ Failed to join worker task: {}", e);
error!("❌ Failed to join actor task: {}", e);
std::process::exit(1);
}
}
@@ -142,7 +142,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// Setup logging based on configuration and command line arguments
fn setup_logging(
config: &WorkerConfig,
config: &ActorConfig,
verbose: bool,
no_timestamp: bool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
@@ -187,11 +187,11 @@ mod tests {
#[test]
fn test_config_validation() {
let config_toml = r#"
worker_id = "test_osis"
actor_id = "test_osis"
redis_url = "redis://localhost:6379"
db_path = "/tmp/test_db"
[worker_type]
[actor_type]
type = "sync"
[logging]
@@ -201,20 +201,20 @@ level = "info"
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(config_toml.as_bytes()).unwrap();
let config = WorkerConfig::from_file(temp_file.path()).unwrap();
let config = ActorConfig::from_file(temp_file.path()).unwrap();
assert!(config.is_sync());
assert!(!config.is_async());
assert_eq!(config.worker_id, "test_osis");
assert_eq!(config.actor_id, "test_osis");
}
#[test]
fn test_async_config_rejection() {
let config_toml = r#"
worker_id = "test_osis"
actor_id = "test_osis"
redis_url = "redis://localhost:6379"
db_path = "/tmp/test_db"
[worker_type]
[actor_type]
type = "async"
default_timeout_seconds = 300
@@ -225,7 +225,7 @@ level = "info"
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(config_toml.as_bytes()).unwrap();
let config = WorkerConfig::from_file(temp_file.path()).unwrap();
let config = ActorConfig::from_file(temp_file.path()).unwrap();
assert!(!config.is_sync());
assert!(config.is_async());
// This would be rejected in main() function

View File

@@ -1,11 +1,11 @@
//! System Worker Binary - Asynchronous worker for high-throughput concurrent processing
//! System Actor Binary - Asynchronous actor for high-throughput concurrent processing
use clap::Parser;
use log::{error, info, warn};
use rhailib_worker::async_worker_impl::AsyncWorker;
use rhailib_worker::config::{ConfigError, WorkerConfig};
use rhailib_worker::engine::create_heromodels_engine;
use rhailib_worker::worker_trait::{spawn_worker, WorkerConfig as TraitWorkerConfig};
use baobab_actor::async_actor_impl::AsyncActor;
use baobab_actor::config::{ConfigError, ActorConfig};
use baobab_actor::engine::create_heromodels_engine;
use baobab_actor::actor_trait::{spawn_actor, ActorConfig as TraitActorConfig};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@@ -16,8 +16,8 @@ use tokio::sync::mpsc;
#[command(
name = "system",
version = "0.1.0",
about = "System Worker - Asynchronous Worker with Concurrent Job Processing",
long_about = "An asynchronous worker for Hero framework that processes multiple jobs \
about = "System Actor - Asynchronous Actor with Concurrent Job Processing",
long_about = "An asynchronous actor for Hero framework that processes multiple jobs \
concurrently with timeout support. Ideal for high-throughput scenarios \
where jobs can be executed in parallel."
)]
@@ -26,9 +26,9 @@ struct Args {
#[arg(short, long, help = "Path to TOML configuration file")]
config: PathBuf,
/// Override worker ID from config
#[arg(long, help = "Override worker ID from configuration file")]
worker_id: Option<String>,
/// Override actor ID from config
#[arg(long, help = "Override actor ID from configuration file")]
actor_id: Option<String>,
/// Override Redis URL from config
#[arg(long, help = "Override Redis URL from configuration file")]
@@ -50,8 +50,8 @@ struct Args {
#[arg(long, help = "Remove timestamps from log output")]
no_timestamp: bool,
/// Show worker statistics periodically
#[arg(long, help = "Show periodic worker statistics")]
/// Show actor statistics periodically
#[arg(long, help = "Show periodic actor statistics")]
show_stats: bool,
}
@@ -60,7 +60,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let args = Args::parse();
// Load configuration from TOML file
let mut config = match WorkerConfig::from_file(&args.config) {
let mut config = match ActorConfig::from_file(&args.config) {
Ok(config) => config,
Err(e) => {
eprintln!("Failed to load configuration from {:?}: {}", args.config, e);
@@ -68,17 +68,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
};
// Validate that this is an async worker configuration
// Validate that this is an async actor configuration
if !config.is_async() {
eprintln!("Error: System worker requires an async worker configuration");
eprintln!("Expected: [worker_type] type = \"async\"");
eprintln!("Found: {:?}", config.worker_type);
eprintln!("Error: System actor requires an async actor configuration");
eprintln!("Expected: [actor_type] type = \"async\"");
eprintln!("Found: {:?}", config.actor_type);
std::process::exit(1);
}
// Apply command line overrides
if let Some(worker_id) = args.worker_id {
config.worker_id = worker_id;
if let Some(actor_id) = args.actor_id {
config.actor_id = actor_id;
}
if let Some(redis_url) = args.redis_url {
config.redis_url = redis_url;
@@ -89,7 +89,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Override timeout if specified
if let Some(timeout_secs) = args.timeout {
if let rhailib_worker::config::WorkerType::Async { ref mut default_timeout_seconds } = config.worker_type {
if let baobab_actor::config::ActorType::Async { ref mut default_timeout_seconds } = config.actor_type {
*default_timeout_seconds = timeout_secs;
}
}
@@ -97,8 +97,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Configure logging
setup_logging(&config, args.verbose, args.no_timestamp)?;
info!("🚀 System Worker starting...");
info!("Worker ID: {}", config.worker_id);
info!("🚀 System Actor starting...");
info!("Actor ID: {}", config.actor_id);
info!("Redis URL: {}", config.redis_url);
info!("Database Path: {}", config.db_path);
info!("Preserve Tasks: {}", config.preserve_tasks);
@@ -111,22 +111,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let engine = create_heromodels_engine();
info!("✅ Rhai engine initialized");
// Create worker configuration for the trait-based interface
let mut worker_config = TraitWorkerConfig::new(
config.worker_id.clone(),
// Create actor configuration for the trait-based interface
let mut actor_config = TraitActorConfig::new(
config.actor_id.clone(),
config.db_path.clone(),
config.redis_url.clone(),
config.preserve_tasks,
);
// Add timeout configuration for async worker
// Add timeout configuration for async actor
if let Some(timeout) = config.get_default_timeout() {
worker_config = worker_config.with_default_timeout(timeout);
actor_config = actor_config.with_default_timeout(timeout);
}
// Create async worker instance
let worker = Arc::new(AsyncWorker::default());
info!("✅ Async worker instance created");
// Create async actor instance
let actor = Arc::new(AsyncActor::default());
info!("✅ Async actor instance created");
// Setup shutdown signal handling
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
@@ -146,36 +146,36 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Spawn statistics reporter if requested
if args.show_stats {
let worker_stats = Arc::clone(&worker);
let actor_stats = Arc::clone(&actor);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
let running_count = worker_stats.running_job_count().await;
let running_count = actor_stats.running_job_count().await;
if running_count > 0 {
info!("📊 Worker Stats: {} jobs currently running", running_count);
info!("📊 Actor Stats: {} jobs currently running", running_count);
} else {
info!("📊 Worker Stats: No jobs currently running");
info!("📊 Actor Stats: No jobs currently running");
}
}
});
}
// Spawn the worker
info!("🔄 Starting worker loop...");
let worker_handle = spawn_worker(worker, engine, shutdown_rx);
// Spawn the actor
info!("🔄 Starting actor loop...");
let actor_handle = spawn_actor(actor, engine, shutdown_rx);
// Wait for the worker to complete
match worker_handle.await {
// Wait for the actor to complete
match actor_handle.await {
Ok(Ok(())) => {
info!("✅ System Worker shut down gracefully");
info!("✅ System Actor shut down gracefully");
}
Ok(Err(e)) => {
error!("❌ System Worker encountered an error: {}", e);
error!("❌ System Actor encountered an error: {}", e);
std::process::exit(1);
}
Err(e) => {
error!("❌ Failed to join worker task: {}", e);
error!("❌ Failed to join actor task: {}", e);
std::process::exit(1);
}
}
@@ -185,7 +185,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// Setup logging based on configuration and command line arguments
fn setup_logging(
config: &WorkerConfig,
config: &ActorConfig,
verbose: bool,
no_timestamp: bool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
@@ -230,11 +230,11 @@ mod tests {
#[test]
fn test_config_validation() {
let config_toml = r#"
worker_id = "test_system"
actor_id = "test_system"
redis_url = "redis://localhost:6379"
db_path = "/tmp/test_db"
[worker_type]
[actor_type]
type = "async"
default_timeout_seconds = 600
@@ -245,21 +245,21 @@ level = "info"
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(config_toml.as_bytes()).unwrap();
let config = WorkerConfig::from_file(temp_file.path()).unwrap();
let config = ActorConfig::from_file(temp_file.path()).unwrap();
assert!(!config.is_sync());
assert!(config.is_async());
assert_eq!(config.worker_id, "test_system");
assert_eq!(config.actor_id, "test_system");
assert_eq!(config.get_default_timeout(), Some(Duration::from_secs(600)));
}
#[test]
fn test_sync_config_rejection() {
let config_toml = r#"
worker_id = "test_system"
actor_id = "test_system"
redis_url = "redis://localhost:6379"
db_path = "/tmp/test_db"
[worker_type]
[actor_type]
type = "sync"
[logging]
@@ -269,7 +269,7 @@ level = "info"
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(config_toml.as_bytes()).unwrap();
let config = WorkerConfig::from_file(temp_file.path()).unwrap();
let config = ActorConfig::from_file(temp_file.path()).unwrap();
assert!(config.is_sync());
assert!(!config.is_async());
// This would be rejected in main() function
@@ -278,11 +278,11 @@ level = "info"
#[test]
fn test_timeout_override() {
let config_toml = r#"
worker_id = "test_system"
actor_id = "test_system"
redis_url = "redis://localhost:6379"
db_path = "/tmp/test_db"
[worker_type]
[actor_type]
type = "async"
default_timeout_seconds = 300
"#;
@@ -290,11 +290,11 @@ default_timeout_seconds = 300
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(config_toml.as_bytes()).unwrap();
let mut config = WorkerConfig::from_file(temp_file.path()).unwrap();
let mut config = ActorConfig::from_file(temp_file.path()).unwrap();
assert_eq!(config.get_default_timeout(), Some(Duration::from_secs(300)));
// Test timeout override
if let rhailib_worker::config::WorkerType::Async { ref mut default_timeout_seconds } = config.worker_type {
if let baobab_actor::config::ActorType::Async { ref mut default_timeout_seconds } = config.actor_type {
*default_timeout_seconds = 600;
}
assert_eq!(config.get_default_timeout(), Some(Duration::from_secs(600)));

View File

@@ -1,14 +1,14 @@
use clap::Parser;
use rhailib_worker::engine::create_heromodels_engine;
use rhailib_worker::spawn_rhai_worker;
use baobab_actor::engine::create_heromodels_engine;
use baobab_actor::spawn_rhai_actor;
use tokio::sync::mpsc;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Worker ID for identification
/// Actor ID for identification
#[arg(short, long)]
worker_id: String,
actor_id: String,
/// Redis URL
#[arg(short, long, default_value = "redis://localhost:6379")]
@@ -19,7 +19,7 @@ struct Args {
preserve_tasks: bool,
/// Root directory for engine database
#[arg(long, default_value = "worker_rhai_temp_db")]
#[arg(long, default_value = "actor_rhai_temp_db")]
db_path: String,
/// Disable timestamps in log output
@@ -41,10 +41,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
log::info!("Rhai Worker (binary) starting with performance-optimized engine.");
log::info!("Rhai Actor (binary) starting with performance-optimized engine.");
log::info!(
"Worker ID: {}, Redis: {}",
args.worker_id,
"Actor ID: {}, Redis: {}",
args.actor_id,
args.redis_url
);
@@ -65,9 +65,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Create shutdown channel (for graceful shutdown, though not used in benchmarks)
let (_shutdown_tx, shutdown_rx) = mpsc::channel::<()>(1);
// Spawn the worker
let worker_handle = spawn_rhai_worker(
args.worker_id,
// Spawn the actor
let actor_handle = spawn_rhai_actor(
args.actor_id,
args.db_path,
engine,
args.redis_url,
@@ -75,20 +75,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
args.preserve_tasks,
);
// Wait for the worker to complete
match worker_handle.await {
// Wait for the actor to complete
match actor_handle.await {
Ok(result) => match result {
Ok(_) => {
log::info!("Worker completed successfully");
log::info!("Actor completed successfully");
Ok(())
}
Err(e) => {
log::error!("Worker failed: {}", e);
log::error!("Actor failed: {}", e);
Err(e)
}
},
Err(e) => {
log::error!("Worker task panicked: {}", e);
log::error!("Actor task panicked: {}", e);
Err(Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
}
}

View File

@@ -1,11 +1,11 @@
# Worker Examples
# Actor Examples
This directory contains example configurations and test scripts for both OSIS and System worker binaries.
This directory contains example configurations and test scripts for both OSIS and System actor binaries.
## Overview
Both examples demonstrate the ping/pong functionality built into the Hero workers:
- Workers automatically detect jobs with script content "ping"
Both examples demonstrate the ping/pong functionality built into the Hero actors:
- Actors 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
@@ -20,20 +20,20 @@ Both examples demonstrate the ping/pong functionality built into the Hero worker
redis-server
```
2. **Rust Environment**: Make sure you can build the worker binaries
2. **Rust Environment**: Make sure you can build the actor binaries
```bash
cd /path/to/herocode/hero/core/worker
cd /path/to/herocode/hero/core/actor
cargo build --bin osis --bin system
```
## OSIS Worker Example
## OSIS Actor Example
**Location**: `examples/osis/`
The OSIS (Operating System Integration Service) worker processes jobs synchronously, one at a time.
The OSIS (Operating System Integration Service) actor processes jobs synchronously, one at a time.
### Files
- `config.toml` - Configuration for the OSIS worker
- `config.toml` - Configuration for the OSIS actor
- `example.sh` - Test script that demonstrates ping/pong functionality
### Usage
@@ -45,31 +45,31 @@ cd examples/osis
### What the script does:
1. Checks Redis connectivity
2. Cleans up any existing jobs
3. Starts the OSIS worker in the background
3. Starts the OSIS actor 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
7. Cleans up actor and Redis data
### Expected Output
```
=== OSIS Worker Example ===
=== OSIS Actor Example ===
✅ Redis is running
✅ OSIS worker started (PID: 12345)
✅ OSIS actor 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.
🎉 All tests passed! OSIS actor is working correctly.
```
## System Worker Example
## System Actor Example
**Location**: `examples/system/`
The System worker processes jobs asynchronously, handling multiple jobs concurrently.
The System actor processes jobs asynchronously, handling multiple jobs concurrently.
### Files
- `config.toml` - Configuration for the System worker (includes async settings)
- `config.toml` - Configuration for the System actor (includes async settings)
- `example.sh` - Test script that demonstrates concurrent ping/pong functionality
### Usage
@@ -81,22 +81,22 @@ cd examples/system
### What the script does:
1. Checks Redis connectivity
2. Cleans up any existing jobs
3. Starts the System worker with stats reporting
3. Starts the System actor 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
8. Cleans up actor and Redis data
### Expected Output
```
=== System Worker Example ===
=== System Actor Example ===
✅ Redis is running
✅ System worker started (PID: 12345)
✅ System actor 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.
🎉 All tests passed! System actor is handling concurrent jobs correctly.
Overall success rate: 15/15
```
@@ -104,12 +104,12 @@ Overall success rate: 15/15
### OSIS Configuration (`examples/osis/config.toml`)
```toml
worker_id = "osis_example_worker"
actor_id = "osis_example_actor"
redis_url = "redis://localhost:6379"
db_path = "/tmp/osis_example_db"
preserve_tasks = false
[worker_type]
[actor_type]
type = "sync"
[logging]
@@ -119,12 +119,12 @@ level = "info"
### System Configuration (`examples/system/config.toml`)
```toml
worker_id = "system_example_worker"
actor_id = "system_example_actor"
redis_url = "redis://localhost:6379"
db_path = "/tmp/system_example_db"
preserve_tasks = false
[worker_type]
[actor_type]
type = "async"
default_timeout_seconds = 30
@@ -135,7 +135,7 @@ level = "info"
## Key Differences
| Feature | OSIS Worker | System Worker |
| Feature | OSIS Actor | System Actor |
|---------|-------------|---------------|
| **Processing** | Sequential (one job at a time) | Concurrent (multiple jobs simultaneously) |
| **Use Case** | System-level operations requiring resource management | High-throughput job processing |
@@ -154,7 +154,7 @@ redis-cli ping
redis-server --loglevel verbose
```
### Worker Compilation Issues
### Actor Compilation Issues
```bash
# Clean and rebuild
cargo clean
@@ -164,7 +164,7 @@ 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
- Check actor logs for detailed error messages
## Extending the Examples
@@ -183,15 +183,15 @@ To test with custom Rhai scripts instead of ping jobs:
### 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
- Adjust timeout values in the System actor configuration
## Architecture Notes
Both examples demonstrate the unified Worker trait architecture:
- **Common Interface**: Both workers implement the same `Worker` trait
Both examples demonstrate the unified Actor trait architecture:
- **Common Interface**: Both actors implement the same `Actor` 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
- **Graceful Shutdown**: Both actors handle SIGTERM/SIGINT properly
This architecture allows for easy extension with new worker types while maintaining consistent behavior and configuration patterns.
This architecture allows for easy extension with new actor types while maintaining consistent behavior and configuration patterns.

View File

@@ -1,9 +1,9 @@
worker_id = "osis_example_worker"
actor_id = "osis_example_actor"
redis_url = "redis://localhost:6379"
db_path = "/tmp/osis_example_db"
preserve_tasks = false
[worker_type]
[actor_type]
type = "sync"
[logging]

View File

@@ -1,8 +1,8 @@
#!/bin/bash
# OSIS Worker Example Script
# This script demonstrates the OSIS worker by:
# 1. Starting the worker with the config.toml
# OSIS Actor Example Script
# This script demonstrates the OSIS actor by:
# 1. Starting the actor with the config.toml
# 2. Sending ping jobs to Redis
# 3. Verifying pong responses
@@ -10,13 +10,13 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config.toml"
WORKER_ID="osis_example_worker"
ACTOR_ID="osis_example_actor"
REDIS_URL="redis://localhost:6379"
echo "=== OSIS Worker Example ==="
echo "=== OSIS Actor Example ==="
echo "Script directory: $SCRIPT_DIR"
echo "Config file: $CONFIG_FILE"
echo "Worker ID: $WORKER_ID"
echo "Actor ID: $ACTOR_ID"
echo "Redis URL: $REDIS_URL"
echo
@@ -32,21 +32,21 @@ 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" del "hero:jobs:$ACTOR_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..."
# Start the OSIS actor in the background
echo "Starting OSIS actor..."
cd "$SCRIPT_DIR/../.."
cargo run --bin osis -- --config "$CONFIG_FILE" &
WORKER_PID=$!
echo "✅ OSIS worker started (PID: $WORKER_PID)"
ACTOR_PID=$!
echo "✅ OSIS actor started (PID: $ACTOR_PID)"
echo
# Wait a moment for the worker to initialize
echo "Waiting for worker to initialize..."
# Wait a moment for the actor to initialize
echo "Waiting for actor to initialize..."
sleep 3
# Function to send a ping job and check for pong response
@@ -62,10 +62,10 @@ send_ping_job() {
script "ping" \
status "Queued" \
created_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
worker_id "$WORKER_ID" > /dev/null
actor_id "$ACTOR_ID" > /dev/null
# Add job to worker queue
redis-cli -u "$REDIS_URL" lpush "hero:jobs:$WORKER_ID" "$job_id" > /dev/null
# Add job to actor queue
redis-cli -u "$REDIS_URL" lpush "hero:jobs:$ACTOR_ID" "$job_id" > /dev/null
# Wait for job completion and check result
local timeout=10
@@ -94,7 +94,7 @@ send_ping_job() {
return 1
}
# Send multiple ping jobs to test the worker
# Send multiple ping jobs to test the actor
echo "Testing ping/pong functionality..."
success_count=0
total_jobs=3
@@ -113,26 +113,26 @@ 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."
echo "🎉 All tests passed! OSIS actor is working correctly."
exit_code=0
else
echo "⚠️ Some tests failed. Check the worker logs for details."
echo "⚠️ Some tests failed. Check the actor 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 "Stopping OSIS actor (PID: $ACTOR_PID)..."
kill $ACTOR_PID 2>/dev/null || true
wait $ACTOR_PID 2>/dev/null || true
echo "✅ Actor 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" del "hero:jobs:$ACTOR_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 ==="
echo "=== OSIS Actor Example Complete ==="
exit $exit_code

View File

@@ -0,0 +1,14 @@
# OSIS Actor Configuration
# Synchronous actor for system-level operations
actor_id = "osis_actor_1"
redis_url = "redis://localhost:6379"
db_path = "/tmp/osis_actor_db"
preserve_tasks = false
[actor_type]
type = "sync"
[logging]
timestamps = true
level = "info"

View File

@@ -3,12 +3,12 @@ use std::path::Path;
use std::env;
use std::io::{self, Write};
/// OSIS Worker Demo Runner
/// OSIS Actor Demo Runner
///
/// This Rust wrapper executes the OSIS worker bash script example.
/// This Rust wrapper executes the OSIS actor 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!("🚀 OSIS Actor Demo");
println!("==================");
println!();
@@ -19,12 +19,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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.");
eprintln!(" Make sure you're running this from the actor crate root directory.");
std::process::exit(1);
}
println!("📁 Script location: {:?}", script_path);
println!("🔧 Executing OSIS worker example...");
println!("🔧 Executing OSIS actor example...");
println!();
// Make sure the script is executable
@@ -50,9 +50,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!();
if status.success() {
println!("✅ OSIS worker demo completed successfully!");
println!("✅ OSIS actor demo completed successfully!");
} else {
println!("❌ OSIS worker demo failed with exit code: {:?}", status.code());
println!("❌ OSIS actor demo failed with exit code: {:?}", status.code());
std::process::exit(status.code().unwrap_or(1));
}

View File

@@ -1,9 +1,9 @@
worker_id = "system_example_worker"
actor_id = "system_example_actor"
redis_url = "redis://localhost:6379"
db_path = "/tmp/system_example_db"
preserve_tasks = false
[worker_type]
[actor_type]
type = "async"
default_timeout_seconds = 30

View File

@@ -1,8 +1,8 @@
#!/bin/bash
# System Worker Example Script
# This script demonstrates the System worker by:
# 1. Starting the worker with the config.toml
# System Actor Example Script
# This script demonstrates the System actor by:
# 1. Starting the actor with the config.toml
# 2. Sending multiple concurrent ping jobs to Redis
# 3. Verifying pong responses
@@ -10,13 +10,13 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config.toml"
WORKER_ID="system_example_worker"
ACTOR_ID="system_example_actor"
REDIS_URL="redis://localhost:6379"
echo "=== System Worker Example ==="
echo "=== System Actor Example ==="
echo "Script directory: $SCRIPT_DIR"
echo "Config file: $CONFIG_FILE"
echo "Worker ID: $WORKER_ID"
echo "Actor ID: $ACTOR_ID"
echo "Redis URL: $REDIS_URL"
echo
@@ -32,21 +32,21 @@ 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" del "hero:jobs:$ACTOR_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..."
# Start the System actor in the background
echo "Starting System actor..."
cd "$SCRIPT_DIR/../.."
cargo run --bin system -- --config "$CONFIG_FILE" --show-stats &
WORKER_PID=$!
echo "✅ System worker started (PID: $WORKER_PID)"
ACTOR_PID=$!
echo "✅ System actor started (PID: $ACTOR_PID)"
echo
# Wait a moment for the worker to initialize
echo "Waiting for worker to initialize..."
# Wait a moment for the actor to initialize
echo "Waiting for actor to initialize..."
sleep 3
# Function to send a ping job (non-blocking)
@@ -62,10 +62,10 @@ send_ping_job() {
script "ping" \
status "Queued" \
created_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
worker_id "$WORKER_ID" > /dev/null
actor_id "$ACTOR_ID" > /dev/null
# Add job to worker queue
redis-cli -u "$REDIS_URL" lpush "hero:jobs:$WORKER_ID" "$job_id" > /dev/null
# Add job to actor queue
redis-cli -u "$REDIS_URL" lpush "hero:jobs:$ACTOR_ID" "$job_id" > /dev/null
echo "$job_id"
}
@@ -129,10 +129,10 @@ 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."
echo "🎉 All tests passed! System actor is handling concurrent jobs correctly."
exit_code=0
else
echo "⚠️ Some tests failed. Check the worker logs for details."
echo "⚠️ Some tests failed. Check the actor logs for details."
exit_code=1
fi
@@ -160,18 +160,18 @@ 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 "Stopping System actor (PID: $ACTOR_PID)..."
kill $ACTOR_PID 2>/dev/null || true
wait $ACTOR_PID 2>/dev/null || true
echo "✅ Actor 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" del "hero:jobs:$ACTOR_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 ==="
echo "=== System Actor Example Complete ==="
total_success=$((success_count + rapid_success))
total_tests=$((total_jobs + rapid_jobs))
echo "Overall success rate: $total_success/$total_tests"

View File

@@ -0,0 +1,15 @@
# System Actor Configuration
# Asynchronous actor for high-throughput concurrent processing
actor_id = "system_actor_1"
redis_url = "redis://localhost:6379"
db_path = "/tmp/system_actor_db"
preserve_tasks = false
[actor_type]
type = "async"
default_timeout_seconds = 300 # 5 minutes
[logging]
timestamps = true
level = "info"

View File

@@ -3,12 +3,12 @@ use std::path::Path;
use std::env;
use std::io::{self, Write};
/// System Worker Demo Runner
/// System Actor Demo Runner
///
/// This Rust wrapper executes the System worker bash script example.
/// This Rust wrapper executes the System actor 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!("🚀 System Actor Demo");
println!("====================");
println!();
@@ -19,12 +19,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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.");
eprintln!(" Make sure you're running this from the actor crate root directory.");
std::process::exit(1);
}
println!("📁 Script location: {:?}", script_path);
println!("🔧 Executing System worker example...");
println!("🔧 Executing System actor example...");
println!();
// Make sure the script is executable
@@ -50,9 +50,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!();
if status.success() {
println!("✅ System worker demo completed successfully!");
println!("✅ System actor demo completed successfully!");
} else {
println!("❌ System worker demo failed with exit code: {:?}", status.code());
println!("❌ System actor demo failed with exit code: {:?}", status.code());
std::process::exit(status.code().unwrap_or(1));
}

View File

@@ -1,13 +1,13 @@
//! # Trait-Based Worker Demo
//! # Trait-Based Actor 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
//! This example demonstrates the new unified actor interface using the Actor trait.
//! It shows how both synchronous and asynchronous actors 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
//! - Unified actor interface using the Actor trait
//! - Both sync and async actor implementations
//! - Shared configuration and spawn logic
//! - Clean shutdown handling
//! - Job processing with different strategies
@@ -16,16 +16,16 @@
//!
//! Make sure Redis is running on localhost:6379, then run:
//! ```bash
//! cargo run --example trait_based_worker_demo
//! cargo run --example trait_based_actor_demo
//! ```
use hero_job::{Job, JobStatus, ScriptType};
use log::{info, warn, error};
use rhailib_worker::{
SyncWorker, AsyncWorker,
spawn_sync_worker, spawn_async_worker,
use baobab_actor::{
SyncActor, AsyncActor,
spawn_sync_actor, spawn_async_actor,
engine::create_heromodels_engine,
worker_trait::{spawn_worker, Worker}
actor_trait::{spawn_actor, Actor}
};
use redis::AsyncCommands;
use std::sync::Arc;
@@ -40,7 +40,7 @@ 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");
info!("Starting Trait-Based Actor Demo");
// Create Redis connection for job creation
let redis_client = redis::Client::open(REDIS_URL)?;
@@ -49,83 +49,83 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Demo 1: Using the unified trait-based interface
info!("=== Demo 1: Unified Trait-Based Interface ===");
// Create shutdown channels for both workers
// Create shutdown channels for both actors
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
// Actors 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")
// Create actor instances using builder pattern
let sync_actor = Arc::new(
SyncActor::builder()
.actor_id("demo_sync_actor")
.db_path("/tmp")
.redis_url("redis://localhost:6379")
.preserve_tasks(false)
.build()
.expect("Failed to build SyncWorker")
.expect("Failed to build SyncActor")
);
let async_worker = Arc::new(
AsyncWorker::builder()
.worker_id("demo_async_worker")
let async_actor = Arc::new(
AsyncActor::builder()
.actor_id("demo_async_actor")
.db_path("/tmp")
.redis_url("redis://localhost:6379")
.default_timeout(Duration::from_secs(300))
.build()
.expect("Failed to build AsyncWorker")
.expect("Failed to build AsyncActor")
);
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 {} actor: {}", sync_actor.actor_type(), sync_actor.actor_id());
let sync_handle = spawn_actor(sync_actor.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);
info!("Spawning {} actor: {}", async_actor.actor_type(), async_actor.actor_id());
let async_handle = spawn_actor(async_actor.clone(), async_engine, async_shutdown_rx);
// Give workers time to start
// Give actors time to start
sleep(Duration::from_secs(1)).await;
// Create and dispatch jobs to both workers
info!("Creating demo jobs for both workers...");
// Create and dispatch jobs to both actors
info!("Creating demo jobs for both actors...");
// Job for sync worker - simple calculation
// Job for sync actor - simple calculation
let sync_job = create_demo_job(
"sync_calculation",
r#"
print("Sync worker: Starting calculation...");
print("Sync actor: Starting calculation...");
let result = 0;
for i in 1..=100 {
result += i;
}
print("Sync worker: Sum of 1-100 = " + result);
print("Sync actor: 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);
dispatch_job(&mut redis_conn, &sync_job, sync_actor.actor_id()).await?;
info!("Dispatched job to sync actor: {}", sync_job.id);
// Job for async worker - with timeout demonstration
// Job for async actor - with timeout demonstration
let async_job = create_demo_job(
"async_calculation",
r#"
print("Async worker: Starting calculation...");
print("Async actor: Starting calculation...");
let result = 1;
for i in 1..=10 {
result *= i;
}
print("Async worker: 10! = " + result);
print("Async actor: 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);
dispatch_job(&mut redis_conn, &async_job, async_actor.actor_id()).await?;
info!("Dispatched job to async actor: {}", async_job.id);
// Monitor job execution
info!("Monitoring job execution for 10 seconds...");
@@ -188,13 +188,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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
// Spawn actors 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(),
info!("Spawning sync actor using convenience function...");
let conv_sync_handle = spawn_sync_actor(
"convenience_sync_actor".to_string(),
"/tmp".to_string(),
conv_sync_engine,
REDIS_URL.to_string(),
@@ -202,9 +202,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
false,
);
info!("Spawning async worker using convenience function...");
let conv_async_handle = spawn_async_worker(
"convenience_async_worker".to_string(),
info!("Spawning async actor using convenience function...");
let conv_async_handle = spawn_async_actor(
"convenience_async_actor".to_string(),
"/tmp".to_string(),
conv_async_engine,
REDIS_URL.to_string(),
@@ -212,15 +212,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Duration::from_secs(20), // 20 second timeout
);
// Give convenience workers time to start
// Give convenience actors time to start
sleep(Duration::from_secs(1)).await;
// Create jobs for convenience workers
// Create jobs for convenience actors
let conv_sync_job = create_demo_job(
"convenience_sync",
r#"
print("Convenience sync worker: Hello World!");
"Hello from convenience sync worker"
print("Convenience sync actor: Hello World!");
"Hello from convenience sync actor"
"#,
None,
).await?;
@@ -228,22 +228,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let conv_async_job = create_demo_job(
"convenience_async",
r#"
print("Convenience async worker: Hello World!");
"Hello from convenience async worker"
print("Convenience async actor: Hello World!");
"Hello from convenience async actor"
"#,
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?;
dispatch_job(&mut redis_conn, &conv_sync_job, "convenience_sync_actor").await?;
dispatch_job(&mut redis_conn, &conv_async_job, "convenience_async_actor").await?;
info!("Dispatched jobs to convenience workers");
info!("Dispatched jobs to convenience actors");
// Wait a bit for jobs to complete
sleep(Duration::from_secs(5)).await;
// Shutdown all workers gracefully
info!("\n=== Shutting Down All Workers ===");
// Shutdown all actors gracefully
info!("\n=== Shutting Down All Actors ===");
info!("Sending shutdown signals...");
let _ = sync_shutdown_tx.send(()).await;
@@ -251,9 +251,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = conv_sync_shutdown_tx.send(()).await;
let _ = conv_async_shutdown_tx.send(()).await;
info!("Waiting for workers to shutdown...");
info!("Waiting for actors to shutdown...");
// Wait for all workers to shutdown
// Wait for all actors to shutdown
let results = tokio::join!(
sync_handle,
async_handle,
@@ -263,23 +263,23 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
match results {
(Ok(Ok(())), Ok(Ok(())), Ok(Ok(())), Ok(Ok(()))) => {
info!("All workers shut down successfully!");
info!("All actors shut down successfully!");
}
_ => {
error!("Some workers encountered errors during shutdown");
error!("Some actors encountered errors during shutdown");
}
}
info!("Trait-Based Worker Demo completed successfully!");
info!("Trait-Based Actor Demo completed successfully!");
// Summary
info!("\n=== Summary ===");
info!("✅ Demonstrated unified Worker trait interface");
info!("✅ Showed both sync and async worker implementations");
info!("✅ Demonstrated unified Actor trait interface");
info!("✅ Showed both sync and async actor 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");
info!("✅ Eliminated code duplication between actor types");
info!("✅ Provided clean, consistent API for all actor operations");
Ok(())
}
@@ -305,17 +305,17 @@ async fn create_demo_job(
Ok(job)
}
/// Dispatch a job to the worker queue
/// Dispatch a job to the actor queue
async fn dispatch_job(
redis_conn: &mut redis::aio::MultiplexedConnection,
job: &Job,
worker_queue: &str,
actor_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);
// Add job to actor queue
let queue_key = format!("hero:job:{}", actor_queue);
let _: () = redis_conn.rpush(&queue_key, &job.id).await?;
Ok(())

View File

@@ -0,0 +1,420 @@
//! # Asynchronous Actor Implementation
//!
//! This module provides an asynchronous actor implementation that can process
//! multiple jobs concurrently with timeout support. Each job is spawned as a
//! separate Tokio task, allowing for parallel execution and proper timeout handling.
//!
//! ## Features
//!
//! - **Concurrent Processing**: Multiple jobs can run simultaneously
//! - **Timeout Support**: Jobs that exceed their timeout are automatically cancelled
//! - **Resource Cleanup**: Proper cleanup of aborted/cancelled jobs
//! - **Non-blocking**: Actor continues processing new jobs while others are running
//! - **Scalable**: Can handle high job throughput with parallel execution
//!
//! ## Usage
//!
//! ```rust
//! use std::sync::Arc;
//! use std::time::Duration;
//! use baobab_actor::async_actor_impl::AsyncActor;
//! use baobab_actor::actor_trait::{spawn_actor, ActorConfig};
//! use baobab_actor::engine::create_heromodels_engine;
//! use tokio::sync::mpsc;
//!
//! let config = ActorConfig::new(
//! "async_actor_1".to_string(),
//! "/path/to/db".to_string(),
//! "redis://localhost:6379".to_string(),
//! false, // preserve_tasks
//! ).with_default_timeout(Duration::from_secs(300));
//!
//! let actor = Arc::new(AsyncActor::new());
//! let engine = create_heromodels_engine();
//! let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
//!
//! let handle = spawn_actor(actor, config, engine, shutdown_rx);
//!
//! // Later, shutdown the actor
//! shutdown_tx.send(()).await.unwrap();
//! handle.await.unwrap().unwrap();
//! ```
use async_trait::async_trait;
use hero_job::{Job, JobStatus};
use log::{debug, error, info, warn};
use rhai::Engine;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use tokio::time::timeout;
use crate::engine::eval_script;
use crate::actor_trait::{Actor, ActorConfig};
use crate::initialize_redis_connection;
/// Represents a running job with its handle and metadata
#[derive(Debug)]
struct RunningJob {
job_id: String,
handle: JoinHandle<()>,
started_at: std::time::Instant,
}
/// Builder for AsyncActor
#[derive(Debug, Default)]
pub struct AsyncActorBuilder {
actor_id: Option<String>,
db_path: Option<String>,
redis_url: Option<String>,
default_timeout: Option<Duration>,
}
impl AsyncActorBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn actor_id<S: Into<String>>(mut self, actor_id: S) -> Self {
self.actor_id = Some(actor_id.into());
self
}
pub fn db_path<S: Into<String>>(mut self, db_path: S) -> Self {
self.db_path = Some(db_path.into());
self
}
pub fn redis_url<S: Into<String>>(mut self, redis_url: S) -> Self {
self.redis_url = Some(redis_url.into());
self
}
pub fn default_timeout(mut self, timeout: Duration) -> Self {
self.default_timeout = Some(timeout);
self
}
pub fn build(self) -> Result<AsyncActor, String> {
Ok(AsyncActor {
actor_id: self.actor_id.ok_or("actor_id is required")?,
db_path: self.db_path.ok_or("db_path is required")?,
redis_url: self.redis_url.ok_or("redis_url is required")?,
default_timeout: self.default_timeout.unwrap_or(Duration::from_secs(300)),
running_jobs: Arc::new(Mutex::new(HashMap::new())),
})
}
}
/// Asynchronous actor that processes jobs concurrently
#[derive(Debug, Clone)]
pub struct AsyncActor {
pub actor_id: String,
pub db_path: String,
pub redis_url: String,
pub default_timeout: Duration,
running_jobs: Arc<Mutex<HashMap<String, RunningJob>>>,
}
impl AsyncActor {
/// Create a new AsyncActorBuilder
pub fn builder() -> AsyncActorBuilder {
AsyncActorBuilder::new()
}
/// Add a running job to the tracking map
async fn add_running_job(&self, job_id: String, handle: JoinHandle<()>) {
let running_job = RunningJob {
job_id: job_id.clone(),
handle,
started_at: std::time::Instant::now(),
};
let mut jobs = self.running_jobs.lock().await;
jobs.insert(job_id.clone(), running_job);
debug!("Async Actor: Added running job '{}'. Total running: {}",
job_id, jobs.len());
}
/// Remove a completed job from the tracking map
async fn remove_running_job(&self, job_id: &str) {
let mut jobs = self.running_jobs.lock().await;
if let Some(job) = jobs.remove(job_id) {
let duration = job.started_at.elapsed();
debug!("Async Actor: Removed completed job '{}' after {:?}. Remaining: {}",
job_id, duration, jobs.len());
}
}
/// Get the count of currently running jobs
pub async fn running_job_count(&self) -> usize {
let jobs = self.running_jobs.lock().await;
jobs.len()
}
/// Cleanup any finished jobs from the running jobs map
async fn cleanup_finished_jobs(&self) {
let mut jobs = self.running_jobs.lock().await;
let mut to_remove = Vec::new();
for (job_id, running_job) in jobs.iter() {
if running_job.handle.is_finished() {
to_remove.push(job_id.clone());
}
}
for job_id in to_remove {
if let Some(job) = jobs.remove(&job_id) {
let duration = job.started_at.elapsed();
debug!("Async Actor: Cleaned up finished job '{}' after {:?}",
job_id, duration);
}
}
}
/// Execute a single job asynchronously with timeout support
async fn execute_job_with_timeout(
job: Job,
engine: Engine,
actor_id: String,
redis_url: String,
job_timeout: Duration,
) {
let job_id = job.id.clone();
info!("Async Actor '{}', Job {}: Starting execution with timeout {:?}",
actor_id, job_id, job_timeout);
// Create a new Redis connection for this job
let mut redis_conn = match initialize_redis_connection(&actor_id, &redis_url).await {
Ok(conn) => conn,
Err(e) => {
error!("Async Actor '{}', Job {}: Failed to initialize Redis connection: {}",
actor_id, job_id, e);
return;
}
};
// Update job status to Started
if let Err(e) = Job::update_status(&mut redis_conn, &job_id, JobStatus::Started).await {
error!("Async Actor '{}', Job {}: Failed to update status to Started: {}",
actor_id, job_id, e);
return;
}
// Create the script execution task
let script_task = async {
// Execute the Rhai script
match eval_script(&engine, &job.script) {
Ok(result) => {
let result_str = format!("{:?}", result);
info!("Async Actor '{}', Job {}: Script executed successfully. Result: {}",
actor_id, job_id, result_str);
// Update job with success result
if let Err(e) = Job::set_result(&mut redis_conn, &job_id, &result_str).await {
error!("Async Actor '{}', Job {}: Failed to set result: {}",
actor_id, job_id, e);
return;
}
if let Err(e) = Job::update_status(&mut redis_conn, &job_id, JobStatus::Finished).await {
error!("Async Actor '{}', Job {}: Failed to update status to Finished: {}",
actor_id, job_id, e);
}
}
Err(e) => {
let error_msg = format!("Script execution error: {}", e);
error!("Async Actor '{}', Job {}: {}", actor_id, job_id, error_msg);
// Update job with error
if let Err(e) = Job::set_error(&mut redis_conn, &job_id, &error_msg).await {
error!("Async Actor '{}', Job {}: Failed to set error: {}",
actor_id, job_id, e);
return;
}
if let Err(e) = Job::update_status(&mut redis_conn, &job_id, JobStatus::Error).await {
error!("Async Actor '{}', Job {}: Failed to update status to Error: {}",
actor_id, job_id, e);
}
}
}
};
// Execute the script with timeout
match timeout(job_timeout, script_task).await {
Ok(()) => {
info!("Async Actor '{}', Job {}: Completed within timeout", actor_id, job_id);
}
Err(_) => {
warn!("Async Actor '{}', Job {}: Timed out after {:?}, marking as error",
actor_id, job_id, job_timeout);
let timeout_msg = format!("Job timed out after {:?}", job_timeout);
if let Err(e) = Job::set_error(&mut redis_conn, &job_id, &timeout_msg).await {
error!("Async Actor '{}', Job {}: Failed to set timeout error: {}",
actor_id, job_id, e);
}
if let Err(e) = Job::update_status(&mut redis_conn, &job_id, JobStatus::Error).await {
error!("Async Actor '{}', Job {}: Failed to update status to Error after timeout: {}",
actor_id, job_id, e);
}
}
}
info!("Async Actor '{}', Job {}: Job processing completed", actor_id, job_id);
}
}
impl Default for AsyncActor {
fn default() -> Self {
// Default AsyncActor with placeholder values
// In practice, use the builder pattern instead
Self {
actor_id: "default_async_actor".to_string(),
db_path: "/tmp".to_string(),
redis_url: "redis://localhost:6379".to_string(),
default_timeout: Duration::from_secs(300),
running_jobs: Arc::new(Mutex::new(HashMap::new())),
}
}
}
#[async_trait]
impl Actor for AsyncActor {
async fn process_job(
&self,
job: Job,
engine: Engine, // Reuse the stateless engine
_redis_conn: &mut redis::aio::MultiplexedConnection,
) {
let job_id = job.id.clone();
let actor_id = &self.actor_id.clone();
// Determine timeout (use job-specific timeout if available, otherwise default)
let job_timeout = if job.timeout.as_secs() > 0 {
job.timeout
} else {
self.default_timeout // Use actor's default timeout
};
info!("Async Actor '{}', Job {}: Spawning job execution task with timeout {:?}",
actor_id, job_id, job_timeout);
// Clone necessary data for the spawned task
let job_id_clone = job_id.clone();
let actor_id_clone = actor_id.clone();
let actor_id_debug = actor_id.clone(); // Additional clone for debug statement
let job_id_debug = job_id.clone(); // Additional clone for debug statement
let redis_url_clone = self.redis_url.clone();
let running_jobs_clone = Arc::clone(&self.running_jobs);
// Spawn the job execution task
let job_handle = tokio::spawn(async move {
Self::execute_job_with_timeout(
job,
engine,
actor_id_clone,
redis_url_clone,
job_timeout,
).await;
// Remove this job from the running jobs map when it completes
let mut jobs = running_jobs_clone.lock().await;
if let Some(running_job) = jobs.remove(&job_id_clone) {
let duration = running_job.started_at.elapsed();
debug!("Async Actor '{}': Removed completed job '{}' after {:?}",
actor_id_debug, job_id_debug, duration);
}
});
// Add the job to the running jobs map
self.add_running_job(job_id, job_handle).await;
// Cleanup finished jobs periodically
self.cleanup_finished_jobs().await;
}
fn actor_type(&self) -> &'static str {
"Async"
}
fn actor_id(&self) -> &str {
&self.actor_id
}
fn redis_url(&self) -> &str {
&self.redis_url
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::create_heromodels_engine;
use hero_job::ScriptType;
#[tokio::test]
async fn test_async_actor_creation() {
let actor = AsyncActor::new();
assert_eq!(actor.actor_type(), "Async");
assert_eq!(actor.running_job_count().await, 0);
}
#[tokio::test]
async fn test_async_actor_default() {
let actor = AsyncActor::default();
assert_eq!(actor.actor_type(), "Async");
}
#[tokio::test]
async fn test_async_actor_job_tracking() {
let actor = AsyncActor::new();
// Simulate adding a job
let handle = tokio::spawn(async {
tokio::time::sleep(Duration::from_millis(100)).await;
});
actor.add_running_job("job_1".to_string(), handle).await;
assert_eq!(actor.running_job_count().await, 1);
// Wait for job to complete
tokio::time::sleep(Duration::from_millis(200)).await;
actor.cleanup_finished_jobs().await;
assert_eq!(actor.running_job_count().await, 0);
}
#[tokio::test]
async fn test_async_actor_process_job_interface() {
let actor = AsyncActor::new();
let engine = create_heromodels_engine();
// Create a simple test job
let job = Job::new(
"test_caller".to_string(),
"test_context".to_string(),
r#"print("Hello from async actor test!"); 42"#.to_string(),
ScriptType::OSIS,
);
let config = ActorConfig::new(
"test_async_actor".to_string(),
"/tmp".to_string(),
"redis://localhost:6379".to_string(),
false,
).with_default_timeout(Duration::from_secs(60));
// Note: This test doesn't actually connect to Redis, it just tests the interface
// In a real test environment, you'd need a Redis instance or mock
// The process_job method should be callable (interface test)
// actor.process_job(job, engine, &mut redis_conn, &config).await;
// For now, just verify the actor was created successfully
assert_eq!(actor.actor_type(), "Async");
}
}

View File

@@ -0,0 +1,250 @@
//! Actor Configuration Module - TOML-based configuration for Hero actors
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use std::time::Duration;
/// Actor configuration loaded from TOML file
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActorConfig {
/// Actor identification
pub actor_id: String,
/// Redis connection URL
pub redis_url: String,
/// Database path for Rhai engine
pub db_path: String,
/// Whether to preserve task details after completion
#[serde(default = "default_preserve_tasks")]
pub preserve_tasks: bool,
/// Actor type configuration
pub actor_type: ActorType,
/// Logging configuration
#[serde(default)]
pub logging: LoggingConfig,
}
/// Actor type configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ActorType {
/// Synchronous actor configuration
#[serde(rename = "sync")]
Sync,
/// Asynchronous actor configuration
#[serde(rename = "async")]
Async {
/// Default timeout for jobs in seconds
#[serde(default = "default_timeout_seconds")]
default_timeout_seconds: u64,
},
}
/// Logging configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
/// Whether to include timestamps in log output
#[serde(default = "default_timestamps")]
pub timestamps: bool,
/// Log level (trace, debug, info, warn, error)
#[serde(default = "default_log_level")]
pub level: String,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
timestamps: default_timestamps(),
level: default_log_level(),
}
}
}
impl ActorConfig {
/// Load configuration from TOML file
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
let content = fs::read_to_string(&path)
.map_err(|e| ConfigError::IoError(format!("Failed to read config file: {}", e)))?;
let config: ActorConfig = toml::from_str(&content)
.map_err(|e| ConfigError::ParseError(format!("Failed to parse TOML: {}", e)))?;
config.validate()?;
Ok(config)
}
/// Validate the configuration
fn validate(&self) -> Result<(), ConfigError> {
if self.actor_id.is_empty() {
return Err(ConfigError::ValidationError("actor_id cannot be empty".to_string()));
}
if self.redis_url.is_empty() {
return Err(ConfigError::ValidationError("redis_url cannot be empty".to_string()));
}
if self.db_path.is_empty() {
return Err(ConfigError::ValidationError("db_path cannot be empty".to_string()));
}
// Validate log level
match self.logging.level.to_lowercase().as_str() {
"trace" | "debug" | "info" | "warn" | "error" => {},
_ => return Err(ConfigError::ValidationError(
format!("Invalid log level: {}. Must be one of: trace, debug, info, warn, error", self.logging.level)
)),
}
Ok(())
}
/// Get the default timeout duration for async actors
pub fn get_default_timeout(&self) -> Option<Duration> {
match &self.actor_type {
ActorType::Sync => None,
ActorType::Async { default_timeout_seconds } => {
Some(Duration::from_secs(*default_timeout_seconds))
}
}
}
/// Check if this is a sync actor configuration
pub fn is_sync(&self) -> bool {
matches!(self.actor_type, ActorType::Sync)
}
/// Check if this is an async actor configuration
pub fn is_async(&self) -> bool {
matches!(self.actor_type, ActorType::Async { .. })
}
}
/// Configuration error types
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("IO error: {0}")]
IoError(String),
#[error("Parse error: {0}")]
ParseError(String),
#[error("Validation error: {0}")]
ValidationError(String),
}
// Default value functions for serde
fn default_preserve_tasks() -> bool {
false
}
fn default_timeout_seconds() -> u64 {
300 // 5 minutes
}
fn default_timestamps() -> bool {
true
}
fn default_log_level() -> String {
"info".to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_sync_actor_config() {
let config_toml = r#"
actor_id = "sync_actor_1"
redis_url = "redis://localhost:6379"
db_path = "/tmp/actor_db"
[actor_type]
type = "sync"
[logging]
timestamps = false
level = "debug"
"#;
let config: ActorConfig = toml::from_str(config_toml).unwrap();
assert_eq!(config.actor_id, "sync_actor_1");
assert!(config.is_sync());
assert!(!config.is_async());
assert_eq!(config.get_default_timeout(), None);
assert!(!config.logging.timestamps);
assert_eq!(config.logging.level, "debug");
}
#[test]
fn test_async_actor_config() {
let config_toml = r#"
actor_id = "async_actor_1"
redis_url = "redis://localhost:6379"
db_path = "/tmp/actor_db"
[actor_type]
type = "async"
default_timeout_seconds = 600
[logging]
timestamps = true
level = "info"
"#;
let config: ActorConfig = toml::from_str(config_toml).unwrap();
assert_eq!(config.actor_id, "async_actor_1");
assert!(!config.is_sync());
assert!(config.is_async());
assert_eq!(config.get_default_timeout(), Some(Duration::from_secs(600)));
assert!(config.logging.timestamps);
assert_eq!(config.logging.level, "info");
}
#[test]
fn test_config_from_file() {
let config_toml = r#"
actor_id = "test_actor"
redis_url = "redis://localhost:6379"
db_path = "/tmp/test_db"
[actor_type]
type = "sync"
"#;
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(config_toml.as_bytes()).unwrap();
let config = ActorConfig::from_file(temp_file.path()).unwrap();
assert_eq!(config.actor_id, "test_actor");
assert!(config.is_sync());
}
#[test]
fn test_config_validation() {
let config_toml = r#"
actor_id = ""
redis_url = "redis://localhost:6379"
db_path = "/tmp/test_db"
[actor_type]
type = "sync"
"#;
let result: Result<ActorConfig, _> = toml::from_str(config_toml);
assert!(result.is_ok());
let config = result.unwrap();
assert!(config.validate().is_err());
}
}

View File

@@ -0,0 +1,260 @@
//! # Rhai Engine Module
//!
//! The central Rhai scripting engine for the heromodels ecosystem. This module provides
//! a unified interface for creating, configuring, and executing Rhai scripts with access
//! to all business domain modules.
//!
//! ## Features
//!
//! - **Unified Engine Creation**: Pre-configured Rhai engine with all DSL modules
//! - **Script Execution Utilities**: Direct evaluation, file-based execution, and AST compilation
//! - **Mock Database System**: Complete testing environment with seeded data
//! - **Feature-Based Architecture**: Modular compilation based on required domains
//!
//! ## Quick Start
//!
//! ```rust
//! use baobab_actor::engine::{create_heromodels_engine, eval_script};
//!
//! // Create a fully configured engine
//! let engine = create_heromodels_engine();
//!
//! // Execute a business logic script
//! let result = eval_script(&engine, r#"
//! let company = new_company()
//! .name("Acme Corp")
//! .business_type("global");
//! company.name
//! "#)?;
//!
//! println!("Company name: {}", result.as_string().unwrap());
//! ```
//!
//! ## Available Features
//!
//! - `calendar` (default): Calendar and event management
//! - `finance` (default): Financial accounts, assets, and marketplace
//! - `flow`: Workflow and approval processes
//! - `legal`: Contract and legal document management
//! - `projects`: Project and task management
//! - `biz`: Business operations and entities
use rhai::{Engine, EvalAltResult, Scope, AST};
use std::fs;
use std::path::Path;
/// Creates a fully configured Rhai engine with all available DSL modules.
///
/// This function creates a new Rhai engine and registers all available heromodels
/// DSL modules based on the enabled features. The engine comes pre-configured
/// with all necessary functions and types for business logic scripting.
///
/// # Returns
///
/// A fully configured `Engine` instance ready for script execution.
///
/// # Features
///
/// The engine includes modules based on enabled Cargo features:
/// - `calendar`: Calendar and event management functions
/// - `finance`: Financial accounts, assets, and marketplace operations
/// - `flow`: Workflow and approval process management
/// - `legal`: Contract and legal document handling
/// - `projects`: Project and task management
/// - `biz`: General business operations and entities
///
/// # Example
///
/// ```rust
/// use baobab_actor::engine::create_heromodels_engine;
///
/// let engine = create_heromodels_engine();
///
/// // The engine is now ready to execute business logic scripts
/// let result = engine.eval::<String>(r#"
/// "Hello from heromodels engine!"
/// "#)?;
/// ```
///
/// # Performance Notes
///
/// The engine is optimized for production use with reasonable defaults for
/// operation limits, expression depth, and memory usage. For benchmarking
/// or special use cases, you may want to adjust these limits after creation.
// pub fn create_heromodels_engine() -> Engine {
// let mut engine = Engine::new();
// // Register all heromodels Rhai modules
// baobab_dsl::register_dsl_modules(&mut engine);
// engine
// }
/// Evaluates a Rhai script string and returns the result.
///
/// This function provides a convenient way to execute Rhai script strings directly
/// using the provided engine. It's suitable for one-off script execution or when
/// the script content is dynamically generated.
///
/// # Arguments
///
/// * `engine` - The Rhai engine to use for script execution
/// * `script` - The Rhai script content as a string
///
/// # Returns
///
/// * `Ok(Dynamic)` - The result of script execution
/// * `Err(Box<EvalAltResult>)` - Script compilation or execution error
///
/// # Example
///
/// ```rust
/// use baobab_actor::engine::{create_heromodels_engine, eval_script};
///
/// let engine = create_heromodels_engine();
/// let result = eval_script(&engine, r#"
/// let x = 42;
/// let y = 8;
/// x + y
/// "#)?;
/// assert_eq!(result.as_int().unwrap(), 50);
/// ```
pub fn eval_script(
engine: &Engine,
script: &str,
) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
engine.eval(script)
}
/// Evaluates a Rhai script from a file and returns the result.
///
/// This function reads a Rhai script from the filesystem and executes it using
/// the provided engine. It handles file reading errors gracefully and provides
/// meaningful error messages.
///
/// # Arguments
///
/// * `engine` - The Rhai engine to use for script execution
/// * `file_path` - Path to the Rhai script file
///
/// # Returns
///
/// * `Ok(Dynamic)` - The result of script execution
/// * `Err(Box<EvalAltResult>)` - File reading, compilation, or execution error
///
/// # Example
///
/// ```rust
/// use baobab_actor::engine::{create_heromodels_engine, eval_file};
/// use std::path::Path;
///
/// let engine = create_heromodels_engine();
/// let result = eval_file(&engine, Path::new("scripts/business_logic.rhai"))?;
/// println!("Script result: {:?}", result);
/// ```
///
/// # Error Handling
///
/// File reading errors are converted to Rhai `ErrorSystem` variants with
/// descriptive messages including the file path that failed to load.
pub fn eval_file(
engine: &Engine,
file_path: &Path,
) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
let script_content = fs::read_to_string(file_path).map_err(|e| {
Box::new(EvalAltResult::ErrorSystem(
format!("Failed to read script file '{}': {}", file_path.display(), e),
e.into(),
))
})?;
engine.eval(&script_content)
}
/// Compiles a Rhai script string into an Abstract Syntax Tree (AST).
///
/// This function compiles a Rhai script into an AST that can be executed multiple
/// times with different scopes. This is more efficient than re-parsing the script
/// for each execution when the same script needs to be run repeatedly.
///
/// # Arguments
///
/// * `engine` - The Rhai engine to use for compilation
/// * `script` - The Rhai script content as a string
///
/// # Returns
///
/// * `Ok(AST)` - The compiled Abstract Syntax Tree
/// * `Err(Box<EvalAltResult>)` - Script compilation error
///
/// # Example
///
/// ```rust
/// use baobab_actor::engine::{create_heromodels_engine, compile_script, run_ast};
/// use rhai::Scope;
///
/// let engine = create_heromodels_engine();
/// let ast = compile_script(&engine, r#"
/// let company = new_company().name(company_name);
/// save_company(company)
/// "#)?;
///
/// // Execute the compiled script multiple times with different variables
/// let mut scope1 = Scope::new();
/// scope1.push("company_name", "Acme Corp");
/// let result1 = run_ast(&engine, &ast, &mut scope1)?;
///
/// let mut scope2 = Scope::new();
/// scope2.push("company_name", "Tech Startup");
/// let result2 = run_ast(&engine, &ast, &mut scope2)?;
/// ```
pub fn compile_script(engine: &Engine, script: &str) -> Result<AST, Box<rhai::EvalAltResult>> {
Ok(engine.compile(script)?)
}
/// Executes a compiled Rhai script AST with the provided scope.
///
/// This function runs a pre-compiled AST using the provided engine and scope.
/// The scope can contain variables and functions that will be available to
/// the script during execution.
///
/// # Arguments
///
/// * `engine` - The Rhai engine to use for execution
/// * `ast` - The compiled Abstract Syntax Tree to execute
/// * `scope` - Mutable scope containing variables and functions for the script
///
/// # Returns
///
/// * `Ok(Dynamic)` - The result of script execution
/// * `Err(Box<EvalAltResult>)` - Script execution error
///
/// # Example
///
/// ```rust
/// use baobab_actor::engine::{create_heromodels_engine, compile_script, run_ast};
/// use rhai::Scope;
///
/// let engine = create_heromodels_engine();
/// let ast = compile_script(&engine, "x + y")?;
///
/// let mut scope = Scope::new();
/// scope.push("x", 10_i64);
/// scope.push("y", 32_i64);
///
/// let result = run_ast(&engine, &ast, &mut scope)?;
/// assert_eq!(result.as_int().unwrap(), 42);
/// ```
///
/// # Performance Notes
///
/// Using compiled ASTs is significantly more efficient than re-parsing scripts
/// for repeated execution, especially for complex scripts or when executing
/// the same logic with different input parameters.
pub fn run_ast(
engine: &Engine,
ast: &AST,
scope: &mut Scope,
) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
engine.eval_ast_with_scope(scope, ast)
}

View File

@@ -0,0 +1,255 @@
//! # Synchronous Actor Implementation
//!
//! This module provides a synchronous actor implementation that processes jobs
//! one at a time in sequence. This is the original actor behavior that's suitable
//! for scenarios where job execution should not overlap or when resource constraints
//! require sequential processing.
//!
//! ## Features
//!
//! - **Sequential Processing**: Jobs are processed one at a time
//! - **Simple Resource Management**: No concurrent job tracking needed
//! - **Predictable Behavior**: Jobs complete in the order they're received
//! - **Lower Memory Usage**: Only one job active at a time
//!
//! ## Usage
//!
//! ```rust
//! use std::sync::Arc;
//! use baobab_actor::sync_actor::SyncActor;
//! use baobab_actor::actor_trait::{spawn_actor, ActorConfig};
//! use baobab_actor::engine::create_heromodels_engine;
//! use tokio::sync::mpsc;
//!
//! let config = ActorConfig::new(
//! "sync_actor_1".to_string(),
//! "/path/to/db".to_string(),
//! "redis://localhost:6379".to_string(),
//! false, // preserve_tasks
//! );
//!
//! let actor = Arc::new(SyncActor::new());
//! let engine = create_heromodels_engine();
//! let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
//!
//! let handle = spawn_actor(actor, config, engine, shutdown_rx);
//!
//! // Later, shutdown the actor
//! shutdown_tx.send(()).await.unwrap();
//! handle.await.unwrap().unwrap();
//! ```
use async_trait::async_trait;
use hero_job::{Job, JobStatus};
use log::{debug, error, info};
use rhai::Engine;
use crate::engine::eval_script;
use crate::actor_trait::{Actor, ActorConfig};
/// Builder for SyncActor
#[derive(Debug, Default)]
pub struct SyncActorBuilder {
actor_id: Option<String>,
db_path: Option<String>,
redis_url: Option<String>,
preserve_tasks: bool,
}
impl SyncActorBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn actor_id<S: Into<String>>(mut self, actor_id: S) -> Self {
self.actor_id = Some(actor_id.into());
self
}
pub fn db_path<S: Into<String>>(mut self, db_path: S) -> Self {
self.db_path = Some(db_path.into());
self
}
pub fn redis_url<S: Into<String>>(mut self, redis_url: S) -> Self {
self.redis_url = Some(redis_url.into());
self
}
pub fn preserve_tasks(mut self, preserve: bool) -> Self {
self.preserve_tasks = preserve;
self
}
pub fn build(self) -> Result<SyncActor, String> {
Ok(SyncActor {
actor_id: self.actor_id.ok_or("actor_id is required")?,
db_path: self.db_path.ok_or("db_path is required")?,
redis_url: self.redis_url.ok_or("redis_url is required")?,
preserve_tasks: self.preserve_tasks,
})
}
}
/// Synchronous actor that processes jobs sequentially
#[derive(Debug, Clone)]
pub struct SyncActor {
pub actor_id: String,
pub db_path: String,
pub redis_url: String,
pub preserve_tasks: bool,
}
impl SyncActor {
/// Create a new SyncActorBuilder
pub fn builder() -> SyncActorBuilder {
SyncActorBuilder::new()
}
}
impl Default for SyncActor {
fn default() -> Self {
// Default SyncActor with placeholder values
// In practice, use the builder pattern instead
Self {
actor_id: "default_sync_actor".to_string(),
db_path: "/tmp".to_string(),
redis_url: "redis://localhost:6379".to_string(),
preserve_tasks: false,
}
}
}
#[async_trait]
impl Actor for SyncActor {
async fn process_job(
&self,
job: Job,
engine: Engine,
redis_conn: &mut redis::aio::MultiplexedConnection,
) {
let job_id = &job.id;
let actor_id = &self.actor_id;
let db_path = &self.db_path;
info!("Sync Actor '{}', Job {}: Starting sequential processing", actor_id, job_id);
// Update job status to Started
if let Err(e) = Job::update_status(redis_conn, job_id, JobStatus::Started).await {
error!("Sync Actor '{}', Job {}: Failed to update status to Started: {}",
actor_id, job_id, e);
return;
}
// Execute the Rhai script
match eval_script(&engine, &job.script) {
Ok(result) => {
let result_str = format!("{:?}", result);
info!("Sync Actor '{}', Job {}: Script executed successfully. Result: {}",
actor_id, job_id, result_str);
// Update job with success result
if let Err(e) = Job::set_result(redis_conn, job_id, &result_str).await {
error!("Sync Actor '{}', Job {}: Failed to set result: {}",
actor_id, job_id, e);
return;
}
if let Err(e) = Job::update_status(redis_conn, job_id, JobStatus::Finished).await {
error!("Sync Actor '{}', Job {}: Failed to update status to Finished: {}",
actor_id, job_id, e);
}
}
Err(e) => {
let error_msg = format!("Script execution error: {}", e);
error!("Sync Actor '{}', Job {}: {}", actor_id, job_id, error_msg);
// Update job with error
if let Err(e) = Job::set_error(redis_conn, job_id, &error_msg).await {
error!("Sync Actor '{}', Job {}: Failed to set error: {}",
actor_id, job_id, e);
return;
}
if let Err(e) = Job::update_status(redis_conn, job_id, JobStatus::Error).await {
error!("Sync Actor '{}', Job {}: Failed to update status to Error: {}",
actor_id, job_id, e);
}
}
}
// Cleanup job if preserve_tasks is false
if !self.preserve_tasks {
if let Err(e) = Job::delete_from_redis(redis_conn, job_id).await {
error!("Sync Actor '{}', Job {}: Failed to cleanup job: {}",
actor_id, job_id, e);
} else {
debug!("Sync Actor '{}', Job {}: Job cleaned up from Redis", actor_id, job_id);
}
}
info!("Sync Actor '{}', Job {}: Sequential processing completed", actor_id, job_id);
}
fn actor_type(&self) -> &'static str {
"Sync"
}
fn actor_id(&self) -> &str {
&self.actor_id
}
fn redis_url(&self) -> &str {
&self.redis_url
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::create_heromodels_engine;
use hero_job::ScriptType;
use std::time::Duration;
#[tokio::test]
async fn test_sync_actor_creation() {
let actor = SyncActor::new();
assert_eq!(actor.actor_type(), "Sync");
}
#[tokio::test]
async fn test_sync_actor_default() {
let actor = SyncActor::default();
assert_eq!(actor.actor_type(), "Sync");
}
#[tokio::test]
async fn test_sync_actor_process_job_interface() {
let actor = SyncActor::new();
let engine = create_heromodels_engine();
// Create a simple test job
let job = Job::new(
"test_caller".to_string(),
"test_context".to_string(),
r#"print("Hello from sync actor test!"); 42"#.to_string(),
ScriptType::OSIS,
);
let config = ActorConfig::new(
"test_sync_actor".to_string(),
"/tmp".to_string(),
"redis://localhost:6379".to_string(),
false,
);
// Note: This test doesn't actually connect to Redis, it just tests the interface
// In a real test environment, you'd need a Redis instance or mock
// The process_job method should be callable (interface test)
// actor.process_job(job, engine, &mut redis_conn, &config).await;
// For now, just verify the actor was created successfully
assert_eq!(actor.actor_type(), "Sync");
}
}

View File

@@ -4,8 +4,8 @@ version = "0.1.0"
edition = "2021"
[[bin]]
name = "supervisor_worker_demo"
path = "supervisor_worker_demo.rs"
name = "supervisor_actor_demo"
path = "supervisor_actor_demo.rs"
[dependencies]
hero_supervisor = { path = "../supervisor" }

View File

@@ -1,8 +1,8 @@
//! Hero Supervisor Worker Demo
//! Hero Supervisor Actor Demo
//!
//! This example demonstrates the new Hero Supervisor API with:
//! - Synchronous build() method
//! - Asynchronous start_workers() method
//! - Asynchronous start_actors() method
//! - Proper cleanup on program exit
//! - Signal handling for graceful shutdown
@@ -18,21 +18,21 @@ async fn run_supervisor_demo() -> Result<(), Box<dyn std::error::Error>> {
// 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")
.osis_actor("/usr/local/bin/osis_actor")
.sal_actor("/usr/local/bin/sal_actor")
.v_actor("/usr/local/bin/v_actor")
.python_actor("/usr/local/bin/python_actor")
.actor_env_var("REDIS_URL", "redis://127.0.0.1:6379")
.actor_env_var("LOG_LEVEL", "info")
.build()?;
println!("{}", "✅ Supervisor built successfully!".green());
println!("{}", "Starting workers asynchronously...".yellow());
println!("{}", "Starting actors asynchronously...".yellow());
// Start workers asynchronously
supervisor.start_workers().await?;
// Start actors asynchronously
supervisor.start_actors().await?;
println!("{}", "✅ All workers started successfully!".green());
println!("{}", "✅ All actors started successfully!".green());
// Demonstrate job creation and execution
println!("{}", "\n📋 Creating and running test jobs...".cyan().bold());
@@ -43,7 +43,7 @@ async fn run_supervisor_demo() -> Result<(), Box<dyn std::error::Error>> {
// Submit and run the job
match supervisor.new_job()
.script_type(ScriptType::OSIS)
.script("println('Hello from OSIS worker!')")
.script("println('Hello from OSIS actor!')")
.timeout(Duration::from_secs(30))
.await_response().await {
Ok(result) => {
@@ -60,7 +60,7 @@ async fn run_supervisor_demo() -> Result<(), Box<dyn std::error::Error>> {
println!("{}", "\n🛑 Shutdown signal received, cleaning up...".yellow().bold());
// Cleanup workers before exit
// Cleanup actors before exit
supervisor.cleanup_and_shutdown().await?;
println!("{}", "✅ Cleanup completed. Goodbye!".green().bold());

View File

@@ -25,20 +25,20 @@ Where config is toml file with the following structure:
[global]
redis_url = "redis://localhost:6379"
[osis_worker]
binary_path = "/path/to/osis_worker"
[osis_actor]
binary_path = "/path/to/osis_actor"
env_vars = { "VAR1" = "value1", "VAR2" = "value2" }
[sal_worker]
binary_path = "/path/to/sal_worker"
[sal_actor]
binary_path = "/path/to/sal_actor"
env_vars = { "VAR1" = "value1", "VAR2" = "value2" }
[v_worker]
binary_path = "/path/to/v_worker"
[v_actor]
binary_path = "/path/to/v_actor"
env_vars = { "VAR1" = "value1", "VAR2" = "value2" }
[python_worker]
binary_path = "/path/to/python_worker"
[python_actor]
binary_path = "/path/to/python_actor"
env_vars = { "VAR1" = "value1", "VAR2" = "value2" }
```
@@ -46,7 +46,7 @@ env_vars = { "VAR1" = "value1", "VAR2" = "value2" }
Lets have verbosity settings etc.
CLI Offers a few commands:
workers:
actors:
start
stop
restart
@@ -63,4 +63,4 @@ jobs:
logs
list
repl: you can enter interactive mode to run scripts, however predefine caller_id, context_id and worker type so supervisor dispathces jobs accordingly
repl: you can enter interactive mode to run scripts, however predefine caller_id, context_id and actor type so supervisor dispathces jobs accordingly

View File

@@ -43,7 +43,7 @@ struct Args {
struct Config {
global: GlobalConfig,
#[serde(flatten)]
workers: std::collections::HashMap<String, WorkerConfigToml>,
actors: std::collections::HashMap<String, ActorConfigToml>,
}
#[derive(Debug, Deserialize)]
@@ -52,7 +52,7 @@ struct GlobalConfig {
}
#[derive(Debug, Deserialize)]
struct WorkerConfigToml {
struct ActorConfigToml {
binary_path: String,
env_vars: Option<std::collections::HashMap<String, String>>,
}
@@ -60,20 +60,20 @@ struct WorkerConfigToml {
#[derive(Debug, Clone, PartialEq)]
enum TabId {
Dashboard,
Workers,
Actors,
Jobs,
Logs,
}
impl TabId {
fn all() -> Vec<TabId> {
vec![TabId::Dashboard, TabId::Workers, TabId::Jobs, TabId::Logs]
vec![TabId::Dashboard, TabId::Actors, TabId::Jobs, TabId::Logs]
}
fn title(&self) -> &str {
match self {
TabId::Dashboard => "Dashboard",
TabId::Workers => "Workers",
TabId::Actors => "Actors",
TabId::Jobs => "Jobs",
TabId::Logs => "Logs",
}
@@ -167,7 +167,7 @@ fn render_ui(f: &mut Frame, app: &mut App) {
// Render content based on selected tab
match app.current_tab {
TabId::Dashboard => render_dashboard(f, chunks[1], app),
TabId::Workers => render_workers(f, chunks[1], app),
TabId::Actors => render_actors(f, chunks[1], app),
TabId::Jobs => render_jobs(f, chunks[1], app),
TabId::Logs => render_logs(f, chunks[1], app),
}
@@ -180,7 +180,7 @@ fn render_dashboard(f: &mut Frame, area: Rect, app: &App) {
.split(area);
// Status overview - supervisor is already running if we get here
let status_text = "Status: ✓ Running\nWorkers: Started successfully\nJobs: Ready for processing\n\nPress 'q' to quit, Tab to navigate";
let status_text = "Status: ✓ Running\nActors: Started successfully\nJobs: Ready for processing\n\nPress 'q' to quit, Tab to navigate";
let status_paragraph = Paragraph::new(status_text)
.block(Block::default().borders(Borders::ALL).title("System Status"))
@@ -202,9 +202,9 @@ fn render_dashboard(f: &mut Frame, area: Rect, app: &App) {
f.render_widget(logs_list, chunks[1]);
}
fn render_workers(f: &mut Frame, area: Rect, _app: &App) {
let paragraph = Paragraph::new("Workers tab - Status checking not implemented yet to avoid system issues")
.block(Block::default().borders(Borders::ALL).title("Workers"))
fn render_actors(f: &mut Frame, area: Rect, _app: &App) {
let paragraph = Paragraph::new("Actors tab - Status checking not implemented yet to avoid system issues")
.block(Block::default().borders(Borders::ALL).title("Actors"))
.wrap(Wrap { trim: true });
f.render_widget(paragraph, area);
@@ -305,18 +305,18 @@ async fn main() -> Result<()> {
let mut builder = SupervisorBuilder::new()
.redis_url(&config.global.redis_url);
for (worker_name, worker_config) in &config.workers {
match worker_name.as_str() {
"osis_worker" => builder = builder.osis_worker(&worker_config.binary_path),
"sal_worker" => builder = builder.sal_worker(&worker_config.binary_path),
"v_worker" => builder = builder.v_worker(&worker_config.binary_path),
"python_worker" => builder = builder.python_worker(&worker_config.binary_path),
_ => log::warn!("Unknown worker type: {}", worker_name),
for (actor_name, actor_config) in &config.actors {
match actor_name.as_str() {
"osis_actor" => builder = builder.osis_actor(&actor_config.binary_path),
"sal_actor" => builder = builder.sal_actor(&actor_config.binary_path),
"v_actor" => builder = builder.v_actor(&actor_config.binary_path),
"python_actor" => builder = builder.python_actor(&actor_config.binary_path),
_ => log::warn!("Unknown actor type: {}", actor_name),
}
if let Some(env_vars) = &worker_config.env_vars {
if let Some(env_vars) = &actor_config.env_vars {
for (key, value) in env_vars {
builder = builder.worker_env_var(key, value);
builder = builder.actor_env_var(key, value);
}
}
}
@@ -325,11 +325,11 @@ async fn main() -> Result<()> {
.map_err(|e| anyhow::anyhow!("Failed to build supervisor: {}", e))?);
info!("✓ Supervisor built successfully");
// Step 4: Start supervisor and workers
info!("Step 4/4: Starting supervisor and workers...");
supervisor.start_workers().await
.map_err(|e| anyhow::anyhow!("Failed to start workers: {}", e))?;
info!("✓ All workers started successfully");
// Step 4: Start supervisor and actors
info!("Step 4/4: Starting supervisor and actors...");
supervisor.start_actors().await
.map_err(|e| anyhow::anyhow!("Failed to start actors: {}", e))?;
info!("✓ All actors started successfully");
// All initialization successful - now start TUI
info!("Initialization complete - starting TUI...");

View File

@@ -73,7 +73,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Validate script type
match args.script_type.to_lowercase().as_str() {
"osis" | "sal" | "v" | "python" => {
// Valid script types - no worker validation needed since we use hardcoded queues
// Valid script types - no actor validation needed since we use hardcoded queues
}
_ => {
error!("❌ Invalid script type: {}. Valid types: osis, sal, v, python", args.script_type);
@@ -89,7 +89,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!(" Script Type: {}", args.script_type);
info!(" Redis URL: {}", args.redis_url);
info!(" Timeout: {}s", args.timeout);
info!(" Using hardcoded worker queues for script type: {}", args.script_type);
info!(" Using hardcoded actor queues for script type: {}", args.script_type);
info!("");
}

View File

@@ -1,14 +0,0 @@
# 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

@@ -1,15 +0,0 @@
# 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"