rename worker to actor
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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");
|
||||
}
|
||||
|
||||
|
@@ -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
|
@@ -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
|
@@ -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)));
|
@@ -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>)
|
||||
}
|
||||
}
|
@@ -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.
|
@@ -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]
|
@@ -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
|
14
_archive/core/actor/examples/osis_config.toml
Normal file
14
_archive/core/actor/examples/osis_config.toml
Normal 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"
|
@@ -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));
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
15
_archive/core/actor/examples/system_config.toml
Normal file
15
_archive/core/actor/examples/system_config.toml
Normal 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"
|
@@ -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));
|
||||
}
|
||||
|
@@ -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(())
|
420
_archive/core/actor/src/async_worker_impl.rs
Normal file
420
_archive/core/actor/src/async_worker_impl.rs
Normal 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");
|
||||
}
|
||||
}
|
250
_archive/core/actor/src/config.rs
Normal file
250
_archive/core/actor/src/config.rs
Normal 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());
|
||||
}
|
||||
}
|
260
_archive/core/actor/src/engine.rs
Normal file
260
_archive/core/actor/src/engine.rs
Normal 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)
|
||||
}
|
255
_archive/core/actor/src/sync_worker.rs
Normal file
255
_archive/core/actor/src/sync_worker.rs
Normal 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");
|
||||
}
|
||||
}
|
@@ -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" }
|
||||
|
@@ -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());
|
||||
|
@@ -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
|
@@ -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...");
|
||||
|
@@ -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!("");
|
||||
}
|
||||
|
||||
|
@@ -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"
|
@@ -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"
|
Reference in New Issue
Block a user