huskies: merge 918
This commit is contained in:
@@ -431,6 +431,10 @@ pub type ActiveProject = std::sync::Arc<tokio::sync::RwLock<String>>;
|
||||
/// `gateway_event_tx` — when `Some`, the bot will subscribe to the gateway
|
||||
/// status broadcaster and forward [`super::GatewayStatusEvent`]s to its
|
||||
/// configured Matrix rooms with a `[project-name]` prefix.
|
||||
///
|
||||
/// Returns `(abort_handle, shutdown_tx)`. The caller **must** hold
|
||||
/// `shutdown_tx` for the bot's lifetime and send `Some(ShutdownReason)` on it
|
||||
/// before process exit so the bot can announce "going offline" to its rooms.
|
||||
pub fn spawn_gateway_bot(
|
||||
config_dir: &Path,
|
||||
active_project: ActiveProject,
|
||||
@@ -438,7 +442,10 @@ pub fn spawn_gateway_bot(
|
||||
gateway_project_urls: BTreeMap<String, String>,
|
||||
port: u16,
|
||||
gateway_event_tx: Option<tokio::sync::broadcast::Sender<super::GatewayStatusEvent>>,
|
||||
) -> Option<tokio::task::AbortHandle> {
|
||||
) -> (
|
||||
Option<tokio::task::AbortHandle>,
|
||||
tokio::sync::watch::Sender<Option<crate::rebuild::ShutdownReason>>,
|
||||
) {
|
||||
use crate::agents::AgentPool;
|
||||
use crate::services::Services;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
@@ -458,7 +465,8 @@ pub fn spawn_gateway_bot(
|
||||
|
||||
let (shutdown_tx, shutdown_rx) =
|
||||
tokio::sync::watch::channel::<Option<crate::rebuild::ShutdownReason>>(None);
|
||||
std::mem::forget(shutdown_tx);
|
||||
// shutdown_tx is intentionally NOT forgotten — the caller holds it and
|
||||
// sends Some(reason) on gateway shutdown so the bot announces "going offline".
|
||||
|
||||
let agents = std::sync::Arc::new(AgentPool::new(port, watcher_tx.clone()));
|
||||
|
||||
@@ -498,7 +506,7 @@ pub fn spawn_gateway_bot(
|
||||
config_dir.join(".huskies").join("timers.json"),
|
||||
));
|
||||
let gateway_event_rx = gateway_event_tx.map(|tx| tx.subscribe());
|
||||
crate::chat::transport::matrix::spawn_bot(
|
||||
let handle = crate::chat::transport::matrix::spawn_bot(
|
||||
config_dir,
|
||||
watcher_tx,
|
||||
services,
|
||||
@@ -508,5 +516,48 @@ pub fn spawn_gateway_bot(
|
||||
gateway_project_urls,
|
||||
timer_store,
|
||||
gateway_event_rx,
|
||||
)
|
||||
);
|
||||
(handle, shutdown_tx)
|
||||
}
|
||||
|
||||
// ── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Regression test for story 918: `spawn_gateway_bot` must return a live
|
||||
/// `shutdown_tx` (not one dropped via `std::mem::forget`) so that callers
|
||||
/// can signal `Some(reason)` and the bot's shutdown watcher receives it.
|
||||
#[tokio::test]
|
||||
async fn spawn_gateway_bot_shutdown_tx_not_forgotten() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let active = std::sync::Arc::new(tokio::sync::RwLock::new("proj".to_string()));
|
||||
let (event_tx, _) = tokio::sync::broadcast::channel(4);
|
||||
|
||||
let (handle, shutdown_tx) = spawn_gateway_bot(
|
||||
tmp.path(),
|
||||
active,
|
||||
vec!["proj".to_string()],
|
||||
std::collections::BTreeMap::new(),
|
||||
3001,
|
||||
Some(event_tx),
|
||||
);
|
||||
|
||||
// No bot.toml in tmp → no abort handle spawned.
|
||||
assert!(handle.is_none());
|
||||
|
||||
// The shutdown_tx must be live: subscribe a receiver and verify that
|
||||
// sending Some(reason) is observed — this would fail if the sender
|
||||
// had been forgotten (channel closed, send returns Err).
|
||||
let rx = shutdown_tx.subscribe();
|
||||
shutdown_tx
|
||||
.send(Some(crate::rebuild::ShutdownReason::Manual))
|
||||
.expect("shutdown_tx must not be closed (was previously std::mem::forget'd)");
|
||||
assert_eq!(
|
||||
*rx.borrow(),
|
||||
Some(crate::rebuild::ShutdownReason::Manual),
|
||||
"shutdown receiver must see the Manual reason"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user