veza/veza-stream-server/src/transcoding/pipeline/queue.rs
2025-12-03 20:36:56 +01:00

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)
}
}