8a83a26de0
El servidor empuja MatchEvent (Available | Lost) a los consumers cuando
sus inputs cambian de match — sea porque un productor llegó, porque
otro mejor lo desplazó, o porque desapareció.
Mecánica:
- Frame::MatchEvent con MatchEventKind { Available, Lost } y los datos
del match (consumer_flow, producer_session/label/flow, ty, via, pinned).
- Server: SessionTxTable (Arc<Mutex<HashMap<SessionId, mpsc::Sender>>>)
+ LastMatches (último match conocido por consumer/input). En cada
register/unregister, broadcast_match_diffs recomputa con el broker
y emite SOLO los diffs respecto al estado anterior.
- Session::run_post_handshake usa tokio::select! para multiplexar
read_frame del cliente y rx.recv() de su tx push.
- Cleanup ahora también limpia push_table y last_matches y dispara un
broadcast (para notificar a quienes pierden el match).
- Client: VecDeque<MatchEvent> bufferea eventos que llegan mezclados
con respuestas a Ping. API:
- take_event() — non-blocking, drena buffer
- await_event(timeout) — bloquea hasta evento o timeout
- ping() ahora drena MatchEvents intermedios hasta encontrar el Pong.
Capacity del canal push por sesión: 32 frames (try_send no-blocking;
si se llena, los eventos extra se descartan — se documenta como
ephemeral, el cliente puede re-consultar via brahman-status).
Test nuevo en brahman-handshake/tests/handshake.rs:
- match_event_pushed_on_producer_arrival: consumer espera, no recibe
evento → llega productor → recibe Available → productor se va →
recibe Lost.
Example nuevo: brahman-handshake/examples/subscriber.rs — cliente que
loguea cada MatchEvent en tiempo real. Útil para ver la dinámica del
broker. Pings cada 25s para keepalive.
Demo end-to-end verificada (4 eventos, 3 ya cubren el ciclo completo):
T+0.3 alpha llega → Available ← demo.alpha.out
T+0.8 beta llega → (sin evento: alpha gana por orden alfabético)
T+1.3 alpha killed → Available ← demo.beta.out (re-evaluación)
T+1.8 beta killed → Lost ← <none>
El broker emite diff: ningún evento cuando un nuevo productor llega
sin desplazar al ganador actual.
Tests: 28/28 (handshake integ 6→7). cargo check --workspace: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
123 lines
3.9 KiB
Rust
123 lines
3.9 KiB
Rust
//! Mensajes del protocolo de handshake.
|
|
//!
|
|
//! Todos los mensajes que cruzan el wire son variantes de [`Frame`].
|
|
|
|
use brahman_broker::MatchStrategy;
|
|
use brahman_card::TypeRef;
|
|
use serde::{Deserialize, Serialize};
|
|
use ulid::Ulid;
|
|
|
|
/// Identificador de sesión emitido por el servidor en `HelloAck`.
|
|
pub type SessionId = Ulid;
|
|
|
|
/// Saludo inicial del módulo. Lleva la Card completa para que el servidor
|
|
/// la valide e indexe.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Hello {
|
|
/// Versión del schema de Card que el cliente sigue.
|
|
pub schema_version: u16,
|
|
/// Versión del protocolo handshake del cliente.
|
|
pub protocol_version: String,
|
|
/// Tarjeta de Presentación.
|
|
pub card: brahman_card::Card,
|
|
}
|
|
|
|
/// Respuesta del servidor a un `Hello` aceptado.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HelloAck {
|
|
/// Versión del crate del servidor.
|
|
pub server_version: String,
|
|
/// Versión del protocolo soportada por el servidor.
|
|
pub protocol_version: String,
|
|
/// Identificador de sesión asignado.
|
|
pub session: SessionId,
|
|
/// `true` si el Init está vinculado al servidor; `false` si el servidor
|
|
/// corre standalone (modo degradado).
|
|
pub init_attached: bool,
|
|
}
|
|
|
|
/// Latido del cliente. El servidor responde con [`Pong`] llevando su reloj.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Ping {
|
|
pub session: SessionId,
|
|
}
|
|
|
|
/// Respuesta a un `Ping` con timestamp del servidor (ms desde UNIX_EPOCH).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Pong {
|
|
pub timestamp_ms: u64,
|
|
}
|
|
|
|
/// Cierre cooperativo de la sesión por parte del cliente.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Farewell {
|
|
pub session: SessionId,
|
|
}
|
|
|
|
/// Errores del protocolo emitidos por el servidor.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
|
|
pub enum HandshakeError {
|
|
#[error("protocolo incompatible: {0}")]
|
|
ProtocolMismatch(String),
|
|
#[error("card inválida: {0}")]
|
|
InvalidCard(String),
|
|
#[error("schema de card incompatible: cliente={client}, servidor={server}")]
|
|
SchemaMismatch { client: u16, server: u16 },
|
|
#[error("sin autorización: {0}")]
|
|
Unauthorized(String),
|
|
#[error("capacidad requerida no satisfecha: {0}")]
|
|
CapabilityUnmet(String),
|
|
#[error("rechazado: {0}")]
|
|
Rejected(String),
|
|
#[error("error interno: {0}")]
|
|
Internal(String),
|
|
}
|
|
|
|
/// Notificación push del server al consumer: un match disponible o perdido.
|
|
///
|
|
/// El server emite `Available` cuando un productor empieza a satisfacer un
|
|
/// `flow.input` del consumer (ya sea porque el productor acaba de
|
|
/// registrarse, o porque cambió el match anterior). Emite `Lost` cuando
|
|
/// el productor previo dejó de satisfacer el input (desregistro o
|
|
/// cambio de match).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MatchEvent {
|
|
pub kind: MatchEventKind,
|
|
/// Nombre del input del consumer al que aplica el evento.
|
|
pub consumer_flow: String,
|
|
/// Sesión y label del productor (en `Lost` puede ser nil/vacío).
|
|
pub producer_session: SessionId,
|
|
pub producer_label: String,
|
|
pub producer_flow: String,
|
|
/// Tipo del flujo matcheado.
|
|
pub ty: TypeRef,
|
|
/// Estrategia que ganó (relevante en `Available`).
|
|
pub via: MatchStrategy,
|
|
/// `true` si fue resuelto por `pin_to`.
|
|
pub pinned: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum MatchEventKind {
|
|
Available,
|
|
Lost,
|
|
}
|
|
|
|
/// Frame único de wire — discriminada por variante. Cada conexión es un
|
|
/// stream de frames.
|
|
///
|
|
/// Direcciones:
|
|
/// - Cliente → Server: `Hello`, `Ping`, `Farewell`.
|
|
/// - Server → Cliente: `HelloAck`, `Pong`, `Error`, `MatchEvent`.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum Frame {
|
|
Hello(Hello),
|
|
HelloAck(HelloAck),
|
|
Ping(Ping),
|
|
Pong(Pong),
|
|
Farewell(Farewell),
|
|
Error(HandshakeError),
|
|
MatchEvent(MatchEvent),
|
|
}
|