feat(nouser+sidecar): watcher con debounce 150ms + re-publish al broker
Cierra los dos pendientes documentados en 487c457: el spam de eventos
duplicados de notify y la falta de propagación al broker cuando una
Mónada cambia composición.
SidecarPool ahora es idempotente respecto a Card.id: spawn rastrea un
HashMap<Ulid, AbortHandle> y aborta la sesión previa si el id ya
existía. Nuevo drop_session(id) para cerrar Mónadas que desaparecen y
live_sessions() para introspección.
Watcher reorganizado en dos threads: dispatcher filtra notify a un
canal de paths; coordinator agrupa con HashMap<PathBuf, Instant> y
dispara batch sólo cuando todos llevan ≥150ms quietos. Cada batch
re-scanea + re-clusteriza con hidratación + diffea contra prior:
removidas → drop_session, nuevas o con composición distinta → spawn
(que reemplaza la sesión previa). Re-scan global por batch es
deliberado y O(N archivos) — aceptable hasta que duela.
This commit is contained in:
@@ -16,12 +16,14 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
|
||||
use brahman_card::{Card, WitInterface};
|
||||
use brahman_card::{ulid::Ulid, Card, WitInterface};
|
||||
use brahman_handshake::{client::Client, transport};
|
||||
use tokio::task::AbortHandle;
|
||||
use tracing::{info, warn};
|
||||
|
||||
/// Período entre pings al Init.
|
||||
@@ -101,6 +103,11 @@ pub fn spawn_with_handle(config: SidecarConfig) -> std::io::Result<JoinHandle<()
|
||||
/// se dropea, el thread interno termina y todas las sesiones cierran.
|
||||
pub struct SidecarPool {
|
||||
handle: tokio::runtime::Handle,
|
||||
/// Sesiones vivas indexadas por `Card.id`. Permite que un nuevo
|
||||
/// `spawn` con el mismo id aborte la sesión previa — útil cuando
|
||||
/// un módulo (p. ej. `nouser daemon`) re-publica una Mónada cuya
|
||||
/// composición cambió.
|
||||
sessions: Arc<Mutex<HashMap<Ulid, AbortHandle>>>,
|
||||
_thread: JoinHandle<()>,
|
||||
}
|
||||
|
||||
@@ -133,6 +140,7 @@ impl SidecarPool {
|
||||
.map_err(|_| std::io::Error::other("pool runtime no respondió"))?;
|
||||
Ok(Self {
|
||||
handle,
|
||||
sessions: Arc::new(Mutex::new(HashMap::new())),
|
||||
_thread: thread,
|
||||
})
|
||||
}
|
||||
@@ -148,8 +156,36 @@ impl SidecarPool {
|
||||
}
|
||||
|
||||
/// Añade una sesión con configuración custom.
|
||||
///
|
||||
/// Si ya existía una sesión para el mismo `Card.id`, la previa
|
||||
/// se aborta antes de spawnear la nueva. Esto hace `spawn`
|
||||
/// idempotente respecto al id: re-publicar una Mónada cuya
|
||||
/// composición cambió "refresca" la sesión en el broker.
|
||||
pub fn spawn_with_config(&self, config: SidecarConfig) {
|
||||
self.handle.spawn(run_client(config));
|
||||
let card_id = config.card.id;
|
||||
let join = self.handle.spawn(run_client(config));
|
||||
let abort = join.abort_handle();
|
||||
if let Ok(mut sessions) = self.sessions.lock() {
|
||||
if let Some(prev) = sessions.insert(card_id, abort) {
|
||||
prev.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cierra explícitamente la sesión asociada a `card_id`. No-op si
|
||||
/// no había sesión registrada.
|
||||
pub fn drop_session(&self, card_id: Ulid) {
|
||||
if let Ok(mut sessions) = self.sessions.lock() {
|
||||
if let Some(abort) = sessions.remove(&card_id) {
|
||||
abort.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cantidad actual de sesiones vivas (estimada — puede haber
|
||||
/// drift transitorio entre abort y limpieza).
|
||||
pub fn live_sessions(&self) -> usize {
|
||||
self.sessions.lock().map(|s| s.len()).unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user