add hero runner and clean improve runner lib
This commit is contained in:
@@ -1,31 +1,4 @@
|
||||
//! # Runner Trait Abstraction
|
||||
//!
|
||||
//! This module provides a trait-based abstraction for Rhai runners that eliminates
|
||||
//! code duplication between synchronous and asynchronous runner implementations.
|
||||
//!
|
||||
//! The `Runner` trait defines the common interface and behavior, while specific
|
||||
//! implementations handle job processing differently (sync vs async).
|
||||
//!
|
||||
//! ## Architecture
|
||||
//!
|
||||
//! ```text
|
||||
//! ┌─────────────────┐ ┌─────────────────┐
|
||||
//! │ SyncRunner │ │ AsyncRunner │
|
||||
//! │ │ │ │
|
||||
//! │ process_job() │ │ process_job() │
|
||||
//! │ (sequential) │ │ (concurrent) │
|
||||
//! └─────────────────┘ └─────────────────┘
|
||||
//! │ │
|
||||
//! └───────┬───────────────┘
|
||||
//! │
|
||||
//! ┌───────▼───────┐
|
||||
//! │ Runner Trait │
|
||||
//! │ │
|
||||
//! │ spawn() │
|
||||
//! │ config │
|
||||
//! │ common loop │
|
||||
//! └───────────────┘
|
||||
//! ```
|
||||
//! Runner trait abstraction for job processing
|
||||
|
||||
use crate::{Job, JobStatus, Client};
|
||||
use log::{debug, error, info};
|
||||
@@ -69,50 +42,25 @@ impl RunnerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait defining the common interface for Rhai runners
|
||||
///
|
||||
/// This trait abstracts the common functionality between synchronous and
|
||||
/// asynchronous runners, allowing them to share the same spawn logic and
|
||||
/// Redis polling loop while implementing different job processing strategies.
|
||||
/// Trait for job runners
|
||||
pub trait Runner: Send + Sync + 'static {
|
||||
/// Process a single job
|
||||
///
|
||||
/// This is the core method that differentiates runner implementations:
|
||||
/// - Sync runners process jobs sequentially, one at a time
|
||||
/// - Async runners spawn concurrent tasks for each job
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `job` - The job to process
|
||||
///
|
||||
/// Note: The engine is now owned by the runner implementation as a field
|
||||
/// For sync runners, this should be a blocking operation
|
||||
/// For async runners, this can spawn tasks and return immediately
|
||||
/// Process a single job and return the result
|
||||
fn process_job(&self, job: Job) -> Result<String, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
/// Get the runner type name for logging
|
||||
fn runner_type(&self) -> &'static str;
|
||||
|
||||
/// Get runner ID for this runner instance
|
||||
/// Get runner ID
|
||||
fn runner_id(&self) -> &str;
|
||||
|
||||
/// Get Redis URL for this runner instance
|
||||
/// Get Redis URL
|
||||
fn redis_url(&self) -> &str;
|
||||
|
||||
/// Spawn the runner
|
||||
///
|
||||
/// This method provides the common runner loop implementation that both
|
||||
/// sync and async runners can use. It handles:
|
||||
/// - Redis connection setup
|
||||
/// - Job polling from Redis queue
|
||||
/// - Shutdown signal handling
|
||||
/// - Delegating job processing to the implementation
|
||||
///
|
||||
/// Note: The engine is now owned by the runner implementation as a field
|
||||
/// Spawn the runner loop
|
||||
fn spawn(
|
||||
self: Arc<Self>,
|
||||
mut shutdown_rx: mpsc::Receiver<()>,
|
||||
) -> JoinHandle<Result<(), Box<dyn std::error::Error + Send + Sync>>> {
|
||||
) -> JoinHandle<Result<(), Box<dyn std::error::Error + Send + Sync>>>
|
||||
where
|
||||
Self: Sized
|
||||
{
|
||||
tokio::spawn(async move {
|
||||
let runner_id = self.runner_id();
|
||||
let redis_url = self.redis_url();
|
||||
@@ -126,8 +74,7 @@ pub trait Runner: Send + Sync + 'static {
|
||||
|
||||
let queue_key = client.runner_key(runner_id);
|
||||
info!(
|
||||
"{} Runner '{}' starting. Connecting to Redis at {}. Listening on queue: {}",
|
||||
self.runner_type(),
|
||||
"Runner '{}' starting. Connecting to Redis at {}. Listening on queue: {}",
|
||||
runner_id,
|
||||
redis_url,
|
||||
queue_key
|
||||
@@ -135,119 +82,109 @@ pub trait Runner: Send + Sync + 'static {
|
||||
|
||||
let mut redis_conn = initialize_redis_connection(runner_id, redis_url).await?;
|
||||
|
||||
// Main runner loop: poll Redis queue for jobs and process them
|
||||
// Exits on shutdown signal or Redis error
|
||||
loop {
|
||||
let blpop_keys = vec![queue_key.clone()];
|
||||
tokio::select! {
|
||||
// Listen for shutdown signal
|
||||
_ = shutdown_rx.recv() => {
|
||||
info!("{} Runner '{}': Shutdown signal received. Terminating loop.",
|
||||
self.runner_type(), runner_id);
|
||||
info!("Runner '{}': Shutdown signal received", runner_id);
|
||||
break;
|
||||
}
|
||||
// Listen for tasks from Redis
|
||||
blpop_result = redis_conn.blpop(&blpop_keys, BLPOP_TIMEOUT_SECONDS as f64) => {
|
||||
debug!("{} Runner '{}': Attempting BLPOP on queue: {}",
|
||||
self.runner_type(), runner_id, queue_key);
|
||||
|
||||
let response: Option<(String, String)> = match blpop_result {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => {
|
||||
error!("{} Runner '{}': Redis BLPOP error on queue {}: {}. Runner for this circle might stop.",
|
||||
self.runner_type(), runner_id, queue_key, e);
|
||||
error!("Runner '{}': Redis BLPOP error: {}", runner_id, e);
|
||||
return Err(Box::new(e) as Box<dyn std::error::Error + Send + Sync>);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((_queue_name_recv, job_id)) = response {
|
||||
info!("{} Runner '{}' received job_id: {} from queue: {}",
|
||||
self.runner_type(), runner_id, job_id, _queue_name_recv);
|
||||
info!("Runner '{}' received job: {}", runner_id, job_id);
|
||||
|
||||
// Load the job from Redis
|
||||
// Load and process job
|
||||
match client.load_job_from_redis(&job_id).await {
|
||||
Ok(job) => {
|
||||
// Check for ping job and handle it directly
|
||||
if job.payload.trim() == "ping" {
|
||||
info!("{} Runner '{}': Received ping job '{}', responding with pong",
|
||||
self.runner_type(), runner_id, job_id);
|
||||
|
||||
// Update job status to started
|
||||
if let Err(e) = client.set_job_status(&job_id, JobStatus::Started).await {
|
||||
error!("{} Runner '{}': Failed to update ping job '{}' status to Started: {}",
|
||||
self.runner_type(), runner_id, job_id, e);
|
||||
}
|
||||
|
||||
// Set result to "pong" and mark as finished
|
||||
if let Err(e) = client.set_result(&job_id, "pong").await {
|
||||
error!("{} Runner '{}': Failed to set ping job '{}' result: {}",
|
||||
self.runner_type(), runner_id, job_id, e);
|
||||
}
|
||||
|
||||
if let Err(e) = client.set_job_status(&job_id, JobStatus::Finished).await {
|
||||
error!("{} Runner '{}': Failed to update ping job '{}' status to Finished: {}",
|
||||
self.runner_type(), runner_id, job_id, e);
|
||||
}
|
||||
|
||||
info!("{} Runner '{}': Successfully responded to ping job '{}' with pong",
|
||||
self.runner_type(), runner_id, job_id);
|
||||
handle_ping_job(&client, runner_id, &job_id).await;
|
||||
} else {
|
||||
// Update job status to started
|
||||
if let Err(e) = client.set_job_status(&job_id, JobStatus::Started).await {
|
||||
error!("{} Runner '{}': Failed to update job '{}' status to Started: {}",
|
||||
self.runner_type(), runner_id, job_id, e);
|
||||
}
|
||||
|
||||
// Delegate job processing to the implementation
|
||||
match self.process_job(job) {
|
||||
Ok(result) => {
|
||||
// Set result and mark as finished
|
||||
if let Err(e) = client.set_result(&job_id, &result).await {
|
||||
error!("{} Runner '{}': Failed to set job '{}' result: {}",
|
||||
self.runner_type(), runner_id, job_id, e);
|
||||
}
|
||||
|
||||
if let Err(e) = client.set_job_status(&job_id, JobStatus::Finished).await {
|
||||
error!("{} Runner '{}': Failed to update job '{}' status to Finished: {}",
|
||||
self.runner_type(), runner_id, job_id, e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error_str = format!("{:?}", e);
|
||||
error!("{} Runner '{}': Job '{}' processing failed: {}",
|
||||
self.runner_type(), runner_id, job_id, error_str);
|
||||
|
||||
// Set error and mark as error
|
||||
if let Err(e) = client.set_error(&job_id, &error_str).await {
|
||||
error!("{} Runner '{}': Failed to set job '{}' error: {}",
|
||||
self.runner_type(), runner_id, job_id, e);
|
||||
}
|
||||
|
||||
if let Err(e) = client.set_job_status(&job_id, JobStatus::Error).await {
|
||||
error!("{} Runner '{}': Failed to update job '{}' status to Error: {}",
|
||||
self.runner_type(), runner_id, job_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
process_regular_job(&*self, &client, runner_id, &job_id, job).await;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{} Runner '{}': Failed to load job '{}': {}",
|
||||
self.runner_type(), runner_id, job_id, e);
|
||||
error!("Runner '{}': Failed to load job '{}': {}", runner_id, job_id, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("{} Runner '{}': BLPOP timed out on queue {}. No new tasks.",
|
||||
self.runner_type(), runner_id, queue_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("{} Runner '{}' has shut down.", self.runner_type(), runner_id);
|
||||
info!("Runner '{}' has shut down", runner_id);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle ping job - responds with "pong"
|
||||
async fn handle_ping_job(client: &Client, runner_id: &str, job_id: &str) {
|
||||
info!("Runner '{}': Received ping job '{}'", runner_id, job_id);
|
||||
|
||||
if let Err(e) = client.set_job_status(job_id, JobStatus::Started).await {
|
||||
error!("Runner '{}': Failed to set ping job '{}' status to Started: {}", runner_id, job_id, e);
|
||||
}
|
||||
|
||||
if let Err(e) = client.set_result(job_id, "pong").await {
|
||||
error!("Runner '{}': Failed to set ping job '{}' result: {}", runner_id, job_id, e);
|
||||
}
|
||||
|
||||
if let Err(e) = client.set_job_status(job_id, JobStatus::Finished).await {
|
||||
error!("Runner '{}': Failed to set ping job '{}' status to Finished: {}", runner_id, job_id, e);
|
||||
}
|
||||
|
||||
info!("Runner '{}': Ping job '{}' completed", runner_id, job_id);
|
||||
}
|
||||
|
||||
/// Process regular job - handles job execution and status updates
|
||||
async fn process_regular_job(
|
||||
runner: &dyn Runner,
|
||||
client: &Client,
|
||||
runner_id: &str,
|
||||
job_id: &str,
|
||||
job: Job,
|
||||
) {
|
||||
if let Err(e) = client.set_job_status(job_id, JobStatus::Started).await {
|
||||
error!("Runner '{}': Failed to set job '{}' status to Started: {}", runner_id, job_id, e);
|
||||
}
|
||||
|
||||
match runner.process_job(job) {
|
||||
Ok(result) => {
|
||||
if let Err(e) = client.set_result(job_id, &result).await {
|
||||
error!("Runner '{}': Failed to set job '{}' result: {}", runner_id, job_id, e);
|
||||
}
|
||||
|
||||
if let Err(e) = client.set_job_status(job_id, JobStatus::Finished).await {
|
||||
error!("Runner '{}': Failed to set job '{}' status to Finished: {}", runner_id, job_id, e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error_str = format!("{:?}", e);
|
||||
error!("Runner '{}': Job '{}' failed: {}", runner_id, job_id, error_str);
|
||||
|
||||
if let Err(e) = client.set_error(job_id, &error_str).await {
|
||||
error!("Runner '{}': Failed to set job '{}' error: {}", runner_id, job_id, e);
|
||||
}
|
||||
|
||||
if let Err(e) = client.set_job_status(job_id, JobStatus::Error).await {
|
||||
error!("Runner '{}': Failed to set job '{}' status to Error: {}", runner_id, job_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function to spawn a runner with the trait-based interface
|
||||
///
|
||||
/// This function provides a unified interface for spawning any runner implementation
|
||||
|
||||
Reference in New Issue
Block a user