115 lines
3.7 KiB
Rust
115 lines
3.7 KiB
Rust
use super::job::{TranscodingJob, JobPriority};
|
|
use tokio::sync::mpsc::{self, Sender, Receiver};
|
|
use std::sync::Arc;
|
|
use tokio::sync::Mutex;
|
|
|
|
/// Taille maximale par défaut de la file d'attente
|
|
const DEFAULT_QUEUE_CAPACITY: usize = 100;
|
|
|
|
/// Erreurs de la file d'attente
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum QueueError {
|
|
#[error("Queue is full (backpressure active)")]
|
|
QueueFull,
|
|
#[error("Queue is closed")]
|
|
QueueClosed,
|
|
}
|
|
|
|
/// Gestionnaire de files d'attente prioritaires
|
|
pub struct PriorityQueue {
|
|
urgent_tx: Sender<TranscodingJob>,
|
|
normal_tx: Sender<TranscodingJob>,
|
|
background_tx: Sender<TranscodingJob>,
|
|
|
|
// On garde les receivers dans un Mutex pour que les workers puissent les partager/consommer
|
|
receivers: Arc<Mutex<QueueReceivers>>,
|
|
}
|
|
|
|
struct QueueReceivers {
|
|
urgent_rx: Receiver<TranscodingJob>,
|
|
normal_rx: Receiver<TranscodingJob>,
|
|
background_rx: Receiver<TranscodingJob>,
|
|
}
|
|
|
|
impl PriorityQueue {
|
|
pub fn new(capacity: usize) -> Self {
|
|
let (urgent_tx, urgent_rx) = mpsc::channel(capacity);
|
|
let (normal_tx, normal_rx) = mpsc::channel(capacity);
|
|
let (background_tx, background_rx) = mpsc::channel(capacity);
|
|
|
|
Self {
|
|
urgent_tx,
|
|
normal_tx,
|
|
background_tx,
|
|
receivers: Arc::new(Mutex::new(QueueReceivers {
|
|
urgent_rx,
|
|
normal_rx,
|
|
background_rx,
|
|
})),
|
|
}
|
|
}
|
|
|
|
/// Soumet un job avec backpressure
|
|
pub async fn submit(&self, job: TranscodingJob) -> Result<(), QueueError> {
|
|
let tx = match job.priority {
|
|
JobPriority::Urgent => &self.urgent_tx,
|
|
JobPriority::Normal => &self.normal_tx,
|
|
JobPriority::Background => &self.background_tx,
|
|
};
|
|
|
|
match tx.try_send(job) {
|
|
Ok(_) => Ok(()),
|
|
Err(mpsc::error::TrySendError::Full(_)) => Err(QueueError::QueueFull),
|
|
Err(mpsc::error::TrySendError::Closed(_)) => Err(QueueError::QueueClosed),
|
|
}
|
|
}
|
|
|
|
/// Récupère le prochain job disponible selon la priorité
|
|
/// Urgent > Normal > Background
|
|
pub async fn next_job(&self) -> Option<TranscodingJob> {
|
|
let mut rxs = self.receivers.lock().await;
|
|
|
|
// Stratégie de priorité stricte : on vide d'abord Urgent, puis Normal, etc.
|
|
|
|
// 1. Tenter Urgent (non-bloquant)
|
|
if let Ok(job) = rxs.urgent_rx.try_recv() {
|
|
return Some(job);
|
|
}
|
|
|
|
// 2. Tenter Normal (non-bloquant)
|
|
if let Ok(job) = rxs.normal_rx.try_recv() {
|
|
return Some(job);
|
|
}
|
|
|
|
// 3. Tenter Background (non-bloquant)
|
|
if let Ok(job) = rxs.background_rx.try_recv() {
|
|
return Some(job);
|
|
}
|
|
|
|
// 4. Si rien, on doit attendre sur l'un des trois.
|
|
// Pour contourner l'erreur E0499, on doit "splitter" le borrow ou utiliser une autre approche.
|
|
// Une approche simple avec tokio::select! sur des &mut Receiver est délicate si ils sont dans un MutexGuard.
|
|
// Solution: On utilise `recv()` séquentiellement avec un timeout très court ou `tokio::select!` sur les champs
|
|
// MAIS comme on a le Mutex, on est le seul thread à lire.
|
|
|
|
// On va extraire les références mutables pour le select
|
|
let QueueReceivers {
|
|
urgent_rx,
|
|
normal_rx,
|
|
background_rx,
|
|
} = &mut *rxs;
|
|
|
|
tokio::select! {
|
|
Some(job) = urgent_rx.recv() => Some(job),
|
|
Some(job) = normal_rx.recv() => Some(job),
|
|
Some(job) = background_rx.recv() => Some(job),
|
|
else => None // Tous les channels fermés
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for PriorityQueue {
|
|
fn default() -> Self {
|
|
Self::new(DEFAULT_QUEUE_CAPACITY)
|
|
}
|
|
}
|