use crate::contracts::SupervisedTask;
use crate::Supervisor;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::info;
#[derive(Debug, Clone, Copy, Default)]
pub enum LoadBalancingStrategy {
#[default]
RoundRobin,
Random,
LeastLoaded,
}
pub struct TaskPool {
pub id: String,
pub pool_size: usize,
pub strategy: LoadBalancingStrategy,
current_index: Arc<RwLock<usize>>,
}
impl TaskPool {
pub fn new(id: impl Into<String>, pool_size: usize, strategy: LoadBalancingStrategy) -> Self {
Self {
id: id.into(),
pool_size,
strategy,
current_index: Arc::new(RwLock::new(0)),
}
}
pub async fn get_next_worker(&self) -> usize {
match self.strategy {
LoadBalancingStrategy::RoundRobin => {
let mut index = self.current_index.write().await;
let worker = *index;
*index = (*index + 1) % self.pool_size;
worker
}
LoadBalancingStrategy::Random => {
rand::random_range(0..self.pool_size)
}
LoadBalancingStrategy::LeastLoaded => {
let mut index = self.current_index.write().await;
let worker = *index;
*index = (*index + 1) % self.pool_size;
worker
}
}
}
pub fn build_pool<T, F>(&self, task_factory: F) -> Supervisor
where
T: SupervisedTask + 'static,
F: Fn(usize) -> T,
{
let mut supervisor = Supervisor::new();
for i in 0..self.pool_size {
let task = task_factory(i);
supervisor = supervisor.add(task);
}
info!(pool_id = %self.id, size = self.pool_size, "Created task pool");
supervisor
}
pub fn info(&self) -> PoolInfo {
PoolInfo {
id: self.id.clone(),
pool_size: self.pool_size,
strategy: self.strategy,
}
}
}
#[derive(Debug, Clone)]
pub struct PoolInfo {
pub id: String,
pub pool_size: usize,
pub strategy: LoadBalancingStrategy,
}
pub struct TaskPoolBuilder {
id: String,
pool_size: usize,
strategy: LoadBalancingStrategy,
}
impl TaskPoolBuilder {
pub fn new(id: impl Into<String>) -> Self {
Self {
id: id.into(),
pool_size: 1,
strategy: LoadBalancingStrategy::default(),
}
}
pub fn with_size(mut self, size: usize) -> Self {
self.pool_size = size.max(1);
self
}
pub fn with_strategy(mut self, strategy: LoadBalancingStrategy) -> Self {
self.strategy = strategy;
self
}
pub fn build(self) -> TaskPool {
TaskPool::new(self.id, self.pool_size, self.strategy)
}
pub fn build_and_create<T, F>(self, task_factory: F) -> Supervisor
where
T: SupervisedTask + 'static,
F: Fn(usize) -> T,
{
let pool = self.build();
pool.build_pool(task_factory)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_round_robin_distribution() {
let pool = TaskPool::new("test-pool", 3, LoadBalancingStrategy::RoundRobin);
assert_eq!(pool.get_next_worker().await, 0);
assert_eq!(pool.get_next_worker().await, 1);
assert_eq!(pool.get_next_worker().await, 2);
assert_eq!(pool.get_next_worker().await, 0);
assert_eq!(pool.get_next_worker().await, 1);
}
#[tokio::test]
async fn test_random_distribution() {
let pool = TaskPool::new("test-pool", 5, LoadBalancingStrategy::Random);
for _ in 0..20 {
let worker = pool.get_next_worker().await;
assert!(worker < 5);
}
}
#[tokio::test]
async fn test_pool_builder() {
let pool = TaskPoolBuilder::new("worker-pool")
.with_size(4)
.with_strategy(LoadBalancingStrategy::RoundRobin)
.build();
assert_eq!(pool.id, "worker-pool");
assert_eq!(pool.pool_size, 4);
assert!(matches!(pool.strategy, LoadBalancingStrategy::RoundRobin));
}
#[tokio::test]
async fn test_pool_info() {
let pool = TaskPool::new("info-test", 3, LoadBalancingStrategy::Random);
let info = pool.info();
assert_eq!(info.id, "info-test");
assert_eq!(info.pool_size, 3);
assert!(matches!(info.strategy, LoadBalancingStrategy::Random));
}
}