use backbone_queue::{
QueueService,
redis::RedisQueueBuilder,
types::{QueueMessage, QueuePriority, MessageStatus}
};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{RwLock, Semaphore};
use tokio::time::sleep;
use tokio_util::sync::CancellationToken;
struct WorkerConfig {
max_concurrent_tasks: usize,
poll_interval: Duration,
max_retries: u32,
visibility_timeout: u64,
shutdown_timeout: Duration,
}
impl Default for WorkerConfig {
fn default() -> Self {
Self {
max_concurrent_tasks: 10,
poll_interval: Duration::from_millis(100),
max_retries: 3,
visibility_timeout: 30,
shutdown_timeout: Duration::from_secs(30),
}
}
}
#[derive(Debug, Clone)]
enum ProcessResult {
Success,
RetryableError(String),
PermanentError(String),
}
#[derive(Debug, Default)]
struct WorkerStats {
messages_processed: u64,
messages_failed: u64,
messages_retried: u64,
total_processing_time: Duration,
}
struct QueueWorker {
queue: Arc<dyn QueueService + Send + Sync>,
config: WorkerConfig,
stats: Arc<RwLock<WorkerStats>>,
semaphore: Arc<Semaphore>,
cancellation_token: CancellationToken,
}
impl QueueWorker {
fn new(
queue: Arc<dyn QueueService + Send + Sync>,
config: WorkerConfig,
) -> Self {
Self {
queue,
semaphore: Arc::new(Semaphore::new(config.max_concurrent_tasks)),
stats: Arc::new(RwLock::new(WorkerStats::default())),
config,
cancellation_token: CancellationToken::new(),
}
}
async fn start(&self) -> Result<(), Box<dyn std::error::Error>> {
println!("๐ Starting queue worker pool");
println!("๐ Configuration:");
println!(" - Max concurrent tasks: {}", self.config.max_concurrent_tasks);
println!(" - Poll interval: {:?}", self.config.poll_interval);
println!(" - Max retries: {}", self.config.max_retries);
println!(" - Visibility timeout: {} seconds", self.config.visibility_timeout);
let queue = self.queue.clone();
let stats = self.stats.clone();
let semaphore = self.semaphore.clone();
let config = self.config.clone();
let cancellation_token = self.cancellation_token.clone();
let stats_reporter = tokio::spawn({
let stats = stats.clone();
async move {
let mut interval = tokio::time::interval(Duration::from_secs(10));
loop {
tokio::select! {
_ = interval.tick() => {
let stats = stats.read().await;
Self::print_stats(&*stats);
}
_ = cancellation_token.cancelled() => {
break;
}
}
}
}
});
let worker_handle = tokio::spawn(async move {
let mut consecutive_empty_polls = 0;
let max_empty_polls = 50;
loop {
tokio::select! {
_ = cancellation_token.cancelled() => {
println!("๐ Worker pool shutdown requested");
break;
}
_ = sleep(config.poll_interval) => {
let permit = semaphore.clone().acquire_owned().await;
if permit.is_err() {
continue; }
match queue.dequeue().await {
Ok(Some(message)) => {
consecutive_empty_polls = 0;
let queue_clone = queue.clone();
let stats_clone = stats.clone();
let config_clone = config.clone();
let cancellation_token_clone = cancellation_token.clone();
tokio::spawn(async move {
let _permit = permit;
let start_time = std::time::Instant::now();
let result = Self::process_message(&message).await;
let processing_time = start_time.elapsed();
{
let mut stats = stats_clone.write().await;
stats.total_processing_time += processing_time;
match result {
ProcessResult::Success => {
stats.messages_processed += 1;
println!("โ
Processed message: {}", message.id);
}
ProcessResult::RetryableError(error) => {
if message.receive_count < config_clone.max_retries {
stats.messages_retried += 1;
println!("โ ๏ธ Retrying message: {} - {}", message.id, error);
let _ = queue_clone.nack(&message.id, Some(60)).await;
} else {
stats.messages_failed += 1;
println!("โ Max retries exceeded for message: {} - {}", message.id, error);
let _ = queue_clone.ack(&message.id).await;
}
}
ProcessResult::PermanentError(error) => {
stats.messages_failed += 1;
println!("โ Permanent error for message: {} - {}", message.id, error);
let _ = queue_clone.ack(&message.id).await;
}
}
}
if cancellation_token_clone.is_cancelled() {
let _ = queue_clone.nack(&message.id, None).await;
}
});
}
Ok(None) => {
consecutive_empty_polls += 1;
if consecutive_empty_polls >= max_empty_polls {
sleep(Duration::from_secs(1)).await;
consecutive_empty_polls = 0;
}
}
Err(e) => {
eprintln!("โ Error dequeuing message: {}", e);
sleep(Duration::from_secs(1)).await;
}
}
}
}
}
});
let cancellation_token_clone = self.cancellation_token.clone();
tokio::spawn(async move {
tokio::signal::ctrl_c().await.ok();
println!("\n๐ Ctrl+C received, initiating graceful shutdown...");
cancellation_token_clone.cancel();
});
tokio::select! {
result = worker_handle => {
println!("๐ Worker completed: {:?}", result);
}
_ = tokio::signal::ctrl_c() => {
println!("๐ Immediate shutdown requested");
}
}
stats_reporter.abort();
println!("โณ Waiting for ongoing tasks to complete...");
let _ = tokio::time::timeout(self.config.shutdown_timeout, async {
while self.semaphore.available_permits() < self.config.max_concurrent_tasks {
sleep(Duration::from_millis(100)).await;
}
}).await;
let final_stats = self.stats.read().await;
Self::print_final_stats(&*final_stats);
println!("โ
Worker pool shutdown complete");
Ok(())
}
async fn process_message(message: &QueueMessage) -> ProcessResult {
let payload = message.payload.as_str().unwrap_or("");
let processing_time = match message.priority {
QueuePriority::Critical => Duration::from_millis(100),
QueuePriority::High => Duration::from_millis(200),
QueuePriority::Normal => Duration::from_millis(500),
QueuePriority::Low => Duration::from_millis(1000),
};
sleep(processing_time).await;
if payload.contains("error-retryable") {
return ProcessResult::RetryableError("Simulated retryable error".to_string());
}
if payload.contains("error-permanent") {
return ProcessResult::PermanentError("Simulated permanent error".to_string());
}
if payload.contains("slow") {
sleep(Duration::from_secs(2)).await;
}
match payload {
p if p.contains("notification") => {
println!("๐ง Sending notification: {}", p);
ProcessResult::Success
}
p if p.contains("email") => {
println!("๐ง Processing email: {}", p);
ProcessResult::Success
}
p if p.contains("report") => {
println!("๐ Generating report: {}", p);
ProcessResult::Success
}
p if p.contains("cleanup") => {
println!("๐งน Performing cleanup: {}", p);
ProcessResult::Success
}
_ => {
println!("โ
Processed generic task: {}", payload);
ProcessResult::Success
}
}
}
fn print_stats(stats: &WorkerStats) {
println!("๐ Worker Statistics:");
println!(" - Messages processed: {}", stats.messages_processed);
println!(" - Messages failed: {}", stats.messages_failed);
println!(" - Messages retried: {}", stats.messages_retried);
if stats.messages_processed > 0 {
let avg_processing_time = stats.total_processing_time / stats.messages_processed as u32;
println!(" - Avg processing time: {:?}", avg_processing_time);
}
let success_rate = if stats.messages_processed + stats.messages_failed > 0 {
stats.messages_processed as f64 / (stats.messages_processed + stats.messages_failed) as f64 * 100.0
} else {
0.0
};
println!(" - Success rate: {:.1}%", success_rate);
}
fn print_final_stats(stats: &WorkerStats) {
println!("\n๐ Final Worker Statistics:");
println!("===========================");
let total_messages = stats.messages_processed + stats.messages_failed;
println!(" ๐ Total messages handled: {}", total_messages);
println!(" โ
Successfully processed: {}", stats.messages_processed);
println!(" โ Failed: {}", stats.messages_failed);
println!(" ๐ Retries attempted: {}", stats.messages_retried);
if total_messages > 0 {
let success_rate = stats.messages_processed as f64 / total_messages as f64 * 100.0;
let error_rate = stats.messages_failed as f64 / total_messages as f64 * 100.0;
println!(" ๐ Success rate: {:.1}%", success_rate);
println!(" ๐ Error rate: {:.1}%", error_rate);
if stats.messages_processed > 0 {
let avg_time = stats.total_processing_time / stats.messages_processed as u32;
let throughput = stats.messages_processed as f64 / stats.total_processing_time.as_secs_f64();
println!(" โฑ๏ธ Average processing time: {:?}", avg_time);
println!(" ๐ Throughput: {:.2} messages/sec", throughput);
}
}
println!(" โณ Total processing time: {:?}", stats.total_processing_time);
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
println!("๐ Queue Worker Pool Example");
println!("============================");
println!("๐ก Setting up Redis queue...");
let queue = Arc::new(
RedisQueueBuilder::new()
.url("redis://localhost:6379")
.queue_name("worker_pool_queue")
.key_prefix("worker")
.pool_size(20)
.build()
.await?
);
if !queue.test_connection().await? {
eprintln!("โ Failed to connect to Redis");
return Ok(());
}
println!("๐งน Clearing existing messages...");
queue.purge().await?;
println!("๐ค Populating queue with test messages...");
let test_messages = vec![
("Send welcome email notification", QueuePriority::High),
("Generate daily report", QueuePriority::Normal),
("Process user cleanup task", QueuePriority::Low),
("Send critical alert", QueuePriority::Critical),
("Process payment notification", QueuePriority::High),
("Generate weekly analytics report", QueuePriority::Normal),
("Perform system maintenance cleanup", QueuePriority::Low),
("Handle emergency notification", QueuePriority::Critical),
("Process bulk email notification", QueuePriority::Normal),
("Simulate error-retryable scenario", QueuePriority::Normal),
("Simulate error-permanent scenario", QueuePriority::Low),
("Process slow report generation task", QueuePriority::Low),
];
let mut message_ids = Vec::new();
for (i, (payload, priority)) in test_messages.into_iter().enumerate() {
let message = QueueMessage::builder()
.id(format!("test-msg-{}", i + 1))
.payload(payload)
.priority(priority)
.max_receive_count(3)
.visibility_timeout(30)
.build();
match queue.enqueue(message).await {
Ok(id) => {
message_ids.push(id);
println!(" โ
Enqueued: {}", payload);
}
Err(e) => {
eprintln!(" โ Failed to enqueue: {}", e);
}
}
}
println!("๐ Enqueued {} messages", message_ids.len());
println!("๐ Queue size: {}", queue.size().await?);
let worker_config = WorkerConfig {
max_concurrent_tasks: 5,
poll_interval: Duration::from_millis(50),
max_retries: 2,
visibility_timeout: 30,
shutdown_timeout: Duration::from_secs(10),
};
let worker = QueueWorker::new(queue, worker_config);
worker.start().await?;
println!("\n๐ Worker pool example completed!");
Ok(())
}