Skip to content

Commit e90e4e4

Browse files
committed
fix: RAII cleanup for registries and addon lifecycle [latest]
- Add Drop impl for TrunkRegistrar (cancel parent_cancel) - Add Drop impl for ClusterEventHub (abort background task) - Add shutdown() to Addon trait + shutdown_all() to AddonRegistry - Call shutdown_all() in app::run() during shutdown - Add Drop impl for TelemetryAddon (flush OTel spans) - Add CancellationToken to ACME and Archive addon background tasks
1 parent dfeb4b6 commit e90e4e4

8 files changed

Lines changed: 64 additions & 8 deletions

File tree

src/addons/acme/handlers.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,11 +725,21 @@ fn get_cert_expiry(cert_path: &StdPath) -> anyhow::Result<std::time::SystemTime>
725725
// ============================================================================
726726

727727
/// Spawn the background task that checks certificate expiry and triggers auto-renewal
728-
pub async fn spawn_auto_renew_checker(acme_state: super::AcmeState, app_state: AppState) {
728+
pub async fn spawn_auto_renew_checker(
729+
acme_state: super::AcmeState,
730+
app_state: AppState,
731+
cancel: tokio_util::sync::CancellationToken,
732+
) {
729733
let check_interval = tokio::time::Duration::from_secs(3600); // Check every hour
730734

731735
loop {
732-
tokio::time::sleep(check_interval).await;
736+
tokio::select! {
737+
_ = cancel.cancelled() => {
738+
info!("ACME auto-renew checker stopped");
739+
return;
740+
}
741+
_ = tokio::time::sleep(check_interval) => {}
742+
}
733743

734744
let config = acme_state.auto_renew_config.read().await.clone();
735745

src/addons/acme/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,9 @@ impl Addon for AcmeAddon {
114114
// Spawn background task for certificate expiry checking
115115
let state_clone = self.state.clone();
116116
let app_state = state.clone();
117+
let cancel_token = state.token().child_token();
117118
crate::utils::spawn(async move {
118-
handlers::spawn_auto_renew_checker(state_clone, app_state).await;
119+
handlers::spawn_auto_renew_checker(state_clone, app_state, cancel_token).await;
119120
});
120121

121122
tracing::info!("ACME Addon initialized");

src/addons/archive/mod.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,20 @@ impl ArchiveAddon {
334334
Ok(())
335335
}
336336

337-
async fn run_scheduler(state: AppState, archive_state: ArchiveState) {
337+
async fn run_scheduler(
338+
state: AppState,
339+
archive_state: ArchiveState,
340+
cancel: tokio_util::sync::CancellationToken,
341+
) {
338342
let mut interval = time::interval(time::Duration::from_secs(60));
339343
loop {
340-
interval.tick().await;
344+
tokio::select! {
345+
_ = cancel.cancelled() => {
346+
info!("Archive scheduler stopped");
347+
return;
348+
}
349+
_ = interval.tick() => {}
350+
}
341351

342352
let archive_config = {
343353
let guard = archive_state.config.read().unwrap();
@@ -560,8 +570,9 @@ impl Addon for ArchiveAddon {
560570
}
561571

562572
let archive_state = self.state.clone();
573+
let cancel = state.token().child_token();
563574
crate::utils::spawn(async move {
564-
Self::run_scheduler(state, archive_state).await;
575+
Self::run_scheduler(state, archive_state, cancel).await;
565576
});
566577
Ok(())
567578
}

src/addons/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ pub trait Addon: Send + Sync {
160160
None
161161
}
162162

163+
/// Shutdown the addon, releasing any resources (background tasks, connections, etc.).
164+
/// Called during application shutdown after all servers have stopped.
165+
async fn shutdown(&self) {}
166+
163167
/// Return database migrations for this addon.
164168
fn migrations(&self) -> Vec<Box<dyn sea_orm_migration::MigrationTrait>> {
165169
vec![]

src/addons/registry.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,13 @@ impl AddonRegistry {
419419
}
420420
}
421421

422+
/// Shutdown all addons, releasing resources.
423+
pub async fn shutdown_all(&self) {
424+
for addon in &self.addons {
425+
addon.shutdown().await;
426+
}
427+
}
428+
422429
/// Run database migrations for all enabled addons.
423430
pub async fn run_migrations(&self, db: &sea_orm::DatabaseConnection) -> anyhow::Result<()> {
424431
let manager = sea_orm_migration::SchemaManager::new(db);

src/app.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,10 @@ pub async fn run(state: AppState, mut router: Router) -> Result<()> {
745745
info!("Application shutting down due to cancellation");
746746
}
747747
}
748+
749+
// Shutdown addons (release background tasks, connections, etc.)
750+
state.addon_registry.shutdown_all().await;
751+
748752
Ok(())
749753
}
750754

src/proxy/cluster_event.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::proxy::locator::LocatorEvent;
1212
use crate::proxy::presence::{PresenceManager, PresenceState, PresenceStatus};
1313
use anyhow::{Result, anyhow};
1414
use async_trait::async_trait;
15-
use parking_lot::RwLock;
15+
use parking_lot::{Mutex, RwLock};
1616
use rsipstack::sip::Param;
1717
use rsipstack::sip::headers::typed::CSeq;
1818
use rsipstack::sip::headers::{CallId, ContentType};
@@ -251,6 +251,8 @@ pub struct ClusterEventHub {
251251
handlers: RwLock<Vec<Arc<dyn ClusterEventHandler>>>,
252252
/// Child of the SIP server's cancel token; used to stop the dispatcher.
253253
cancel: tokio_util::sync::CancellationToken,
254+
/// Handle to the background dispatcher task (aborted on drop).
255+
task_handle: Mutex<Option<tokio::task::JoinHandle<()>>>,
254256
}
255257

256258
impl ClusterEventHub {
@@ -270,6 +272,7 @@ impl ClusterEventHub {
270272
peers,
271273
handlers: RwLock::new(Vec::new()),
272274
cancel,
275+
task_handle: Mutex::new(None),
273276
}
274277
}
275278

@@ -286,7 +289,7 @@ impl ClusterEventHub {
286289
let mut rx = self.locator_events.subscribe();
287290
let cancel = self.cancel.clone();
288291
let this = self.clone();
289-
crate::utils::spawn(async move {
292+
let handle = crate::utils::spawn(async move {
290293
loop {
291294
tokio::select! {
292295
_ = cancel.cancelled() => break,
@@ -306,6 +309,7 @@ impl ClusterEventHub {
306309
}
307310
}
308311
});
312+
*self.task_handle.lock() = Some(handle);
309313
}
310314

311315
async fn dispatch_local_locator_event(&self, event: LocatorEvent) {
@@ -542,6 +546,15 @@ impl ClusterEventHub {
542546
}
543547
}
544548

549+
impl Drop for ClusterEventHub {
550+
fn drop(&mut self) {
551+
self.cancel.cancel();
552+
if let Some(handle) = self.task_handle.lock().take() {
553+
handle.abort();
554+
}
555+
}
556+
}
557+
545558
// ── ClusterEventModule (ProxyModule) ────────────────────────────────────────
546559

547560
pub struct ClusterEventModule {

src/proxy/trunk_registrar.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ pub struct TrunkRegistrar {
5151
endpoint: RwLock<Option<EndpointInnerRef>>,
5252
}
5353

54+
impl Drop for TrunkRegistrar {
55+
fn drop(&mut self) {
56+
self.parent_cancel.cancel();
57+
}
58+
}
59+
5460
impl Default for TrunkRegistrar {
5561
fn default() -> Self {
5662
Self::new()

0 commit comments

Comments
 (0)