🧪 Problem-Driven Systems Lab

🦀 Caso 15 — Rust 1.83#

⬅️ Caso 15 · ⚖️ Comparativa de los 7 stacks · 🦀 Perfil de Rust · 🧬 Todos los perfiles

Stack Rust del caso 15. Cola sin capacidad contra cola acotada con polĂ­tica explĂ­cita.

El límite está en el tipo, no en un parámetro#

rust
let (tx, rx) = mpsc::channel::<Msg>();          // Sender<T>     — sin capacidad
let (tx, rx) = mpsc::sync_channel::<Msg>(32);   // SyncSender<T> — acotado

Son tipos distintos con métodos distintos. No se puede «olvidar» el límite de un canal acotado ni pedirle backpressure a uno sin límite: el compilador no deja escribir la confusión.

Comparar con Java es directo: allá ConcurrentLinkedQueue y ArrayBlockingQueue implementan la misma interfaz Queue, así que cambiar una por otra es una línea que compila y saca el freno del sistema. Acá eso no existe — el tipo del canal declara si hay freno o no.

El error de rechazo se lleva el mensaje adentro#

rust
match tx.try_send(msg) {
    Err(TrySendError::Full(msg)) => dlq.push(msg),   // <- msg vuelve
    ...
}

TrySendError::Full(T) devuelve la propiedad del valor rechazado. En Go o Java el mensaje descartado simplemente «sigue en tu mano» por convención; acá el tipo garantiza que no se perdió en el intento y que hay que decidir explícitamente qué hacer con él.

Es exactamente lo que una dead letter queue necesita: el mensaje llega entero, sin clonarlo por las dudas antes de intentar el envĂ­o.

Primitivas nativas#

PrimitivaRol
mpsc::sync_channel(N) → SyncSender<T>La cola acotada. El límite es parte del tipo.
mpsc::channel() → Sender<T>La versión sin capacidad, con otro tipo.
TrySendError::Full(T)El rechazo que devuelve el mensaje.
AtomicI64::fetch_maxProfundidad pico y edad máxima sin lock.

Rutas#

RutaQué muestra
/healthliveness
/produce-unbounded?messages=120&consume_ms=2queue_depth_peak = total producido; oldest_msg_age_ms_peak sin techo
/produce-bounded?...&policy=blockprofundidad acotada a capacity; producer_blocked_ms > 0, nada se pierde
/produce-bounded?...&policy=drop_oldestprofundidad acotada; dropped > 0, el productor nunca se frena
/produce-bounded?...&policy=dead_letterprofundidad acotada; dead_lettered > 0, revisable en /dlq
/queue/stateprofundidad pico, bytes ocupados y edad del mensaje más viejo
/dlq?limit=20contenido de la dead letter queue
/diagnostics/summaryacumulado por variante y polĂ­tica
/reset-lablimpia DLQ y contadores

Parámetros: messages (1–2000), capacity (1–1000), consume_ms (0–100, cuánto tarda el consumidor por mensaje), policy (block · drop_oldest · dead_letter).

Hub#

bash
docker compose -f compose.rust.yml up -d --build
curl "http://127.0.0.1:8700/15/produce-unbounded?messages=120&consume_ms=2"
curl "http://127.0.0.1:8700/15/produce-bounded?messages=120&capacity=32&policy=block&consume_ms=2"
curl "http://127.0.0.1:8700/15/queue/state"

La lecciĂłn que ningĂşn stack cambia#

Las tres polĂ­ticas pagan algo distinto y ninguna es gratis:

PolíticaQué paga
blocklatencia: la lentitud viaja aguas arriba hasta el cliente
drop_oldestdatos: se pierden mensajes, y en silencio salvo que se cuenten
dead_letterdeuda operativa: alguien tiene que mirar esa cola (caso 20)

Una cola sin lĂ­mite parece una cuarta opciĂłn sin costo. No lo es: solo difiere el pago hasta el OOM.

Ver esta carpeta en GitHub ↗