Skip to content

Commit c86746f

Browse files
committed
Handle SIGHUPs as part of the shutdown manager
1 parent fbbbf5b commit c86746f

File tree

5 files changed

+79
-55
lines changed

5 files changed

+79
-55
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ console = "0.15.10"
2323
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
2424
dotenvy = "0.15.7"
2525
figment.workspace = true
26+
futures-util.workspace = true
2627
http-body-util.workspace = true
2728
hyper.workspace = true
2829
ipnetwork = "0.20.0"

crates/cli/src/commands/server.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ use crate::{
2727
shutdown::ShutdownManager,
2828
util::{
2929
database_pool_from_config, mailer_from_config, password_manager_from_config,
30-
policy_factory_from_config, register_sighup, site_config_from_config,
31-
templates_from_config,
30+
policy_factory_from_config, site_config_from_config, templates_from_config,
3231
},
3332
};
3433

@@ -57,7 +56,7 @@ impl Options {
5756
#[allow(clippy::too_many_lines)]
5857
pub async fn run(self, figment: &Figment) -> anyhow::Result<ExitCode> {
5958
let span = info_span!("cli.run.init").entered();
60-
let shutdown = ShutdownManager::new()?;
59+
let mut shutdown = ShutdownManager::new()?;
6160
let config = AppConfig::extract(figment)?;
6261

6362
info!(version = crate::VERSION, "Starting up");
@@ -145,6 +144,7 @@ impl Options {
145144
// Load and compile the templates
146145
let templates =
147146
templates_from_config(&config.templates, &site_config, &url_builder).await?;
147+
shutdown.register_reloadable(&templates);
148148

149149
let http_client = mas_http::reqwest_client();
150150

@@ -186,6 +186,9 @@ impl Options {
186186
shutdown.task_tracker(),
187187
shutdown.soft_shutdown_token(),
188188
);
189+
190+
shutdown.register_reloadable(&activity_tracker);
191+
189192
let trusted_proxies = config.http.trusted_proxies.clone();
190193

191194
// Build a rate limiter.
@@ -197,9 +200,6 @@ impl Options {
197200
// Explicitly the config to properly zeroize secret keys
198201
drop(config);
199202

200-
// Listen for SIGHUP
201-
register_sighup(&templates, &activity_tracker)?;
202-
203203
limiter.start();
204204

205205
let graphql_schema = mas_handlers::graphql_schema(

crates/cli/src/shutdown.rs

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
// SPDX-License-Identifier: AGPL-3.0-only
44
// Please see LICENSE in the repository root for full details.
55

6-
use std::{process::ExitCode, time::Duration};
6+
use std::{future::Future, pin::Pin, process::ExitCode, time::Duration};
77

8+
use futures_util::future::BoxFuture;
9+
use mas_handlers::ActivityTracker;
10+
use mas_templates::Templates;
811
use tokio::signal::unix::{Signal, SignalKind};
912
use tokio_util::{sync::CancellationToken, task::TaskTracker};
1013

@@ -28,7 +31,30 @@ pub struct ShutdownManager {
2831
task_tracker: TaskTracker,
2932
sigterm: Signal,
3033
sigint: Signal,
34+
sighup: Signal,
3135
timeout: Duration,
36+
reload_handlers: Vec<Box<dyn Fn() -> BoxFuture<'static, ()>>>,
37+
}
38+
39+
pub trait Reloadable: Clone + Send {
40+
fn reload(&self) -> impl Future<Output = ()> + Send;
41+
}
42+
43+
impl Reloadable for ActivityTracker {
44+
async fn reload(&self) {
45+
self.flush().await;
46+
}
47+
}
48+
49+
impl Reloadable for Templates {
50+
async fn reload(&self) {
51+
if let Err(err) = self.reload().await {
52+
tracing::error!(
53+
error = &err as &dyn std::error::Error,
54+
"Failed to reload templates"
55+
);
56+
}
57+
}
3258
}
3359

3460
impl ShutdownManager {
@@ -42,6 +68,7 @@ impl ShutdownManager {
4268
let soft_shutdown_token = hard_shutdown_token.child_token();
4369
let sigterm = tokio::signal::unix::signal(SignalKind::terminate())?;
4470
let sigint = tokio::signal::unix::signal(SignalKind::interrupt())?;
71+
let sighup = tokio::signal::unix::signal(SignalKind::hangup())?;
4572
let timeout = Duration::from_secs(60);
4673
let task_tracker = TaskTracker::new();
4774

@@ -51,10 +78,21 @@ impl ShutdownManager {
5178
task_tracker,
5279
sigterm,
5380
sigint,
81+
sighup,
5482
timeout,
83+
reload_handlers: Vec::new(),
5584
})
5685
}
5786

87+
/// Add a handler to be called when the server gets a SIGHUP
88+
pub fn register_reloadable(&mut self, reloadable: &(impl Reloadable + 'static)) {
89+
let reloadable = reloadable.clone();
90+
self.reload_handlers.push(Box::new(move || {
91+
let reloadable = reloadable.clone();
92+
Box::pin(async move { reloadable.reload().await })
93+
}));
94+
}
95+
5896
/// Get a reference to the task tracker
5997
#[must_use]
6098
pub fn task_tracker(&self) -> &TaskTracker {
@@ -76,21 +114,36 @@ impl ShutdownManager {
76114
/// Run until we finish completely shutting down.
77115
pub async fn run(mut self) -> ExitCode {
78116
// Wait for a first signal and trigger the soft shutdown
79-
let likely_crashed = tokio::select! {
80-
() = self.soft_shutdown_token.cancelled() => {
81-
tracing::warn!("Another task triggered a shutdown, it likely crashed! Shutting down");
82-
true
83-
},
84-
85-
_ = self.sigterm.recv() => {
86-
tracing::info!("Shutdown signal received (SIGTERM), shutting down");
87-
false
88-
},
89-
90-
_ = self.sigint.recv() => {
91-
tracing::info!("Shutdown signal received (SIGINT), shutting down");
92-
false
93-
},
117+
let likely_crashed = loop {
118+
tokio::select! {
119+
() = self.soft_shutdown_token.cancelled() => {
120+
tracing::warn!("Another task triggered a shutdown, it likely crashed! Shutting down");
121+
break true;
122+
},
123+
124+
_ = self.sigterm.recv() => {
125+
tracing::info!("Shutdown signal received (SIGTERM), shutting down");
126+
break false;
127+
},
128+
129+
_ = self.sigint.recv() => {
130+
tracing::info!("Shutdown signal received (SIGINT), shutting down");
131+
break false;
132+
},
133+
134+
_ = self.sighup.recv() => {
135+
tracing::info!("Reload signal received (SIGHUP), reloading");
136+
137+
// XXX: if the handler takes a long time, it will block the
138+
// rest of the shutdown process, which is not ideal. We
139+
// should probably have a timeout here
140+
for handler in &self.reload_handlers {
141+
handler().await;
142+
}
143+
144+
tracing::info!("Reloading done");
145+
},
146+
}
94147
};
95148

96149
self.soft_shutdown_token.cancel();

crates/cli/src/util.rs

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ use mas_config::{
1414
};
1515
use mas_data_model::SiteConfig;
1616
use mas_email::{MailTransport, Mailer};
17-
use mas_handlers::{passwords::PasswordManager, ActivityTracker};
17+
use mas_handlers::passwords::PasswordManager;
1818
use mas_policy::PolicyFactory;
1919
use mas_router::UrlBuilder;
2020
use mas_templates::{SiteConfigExt, TemplateLoadingError, Templates};
2121
use sqlx::{
2222
postgres::{PgConnectOptions, PgPoolOptions},
2323
ConnectOptions, PgConnection, PgPool,
2424
};
25-
use tracing::{error, info, log::LevelFilter};
25+
use tracing::log::LevelFilter;
2626

2727
pub async fn password_manager_from_config(
2828
config: &PasswordsConfig,
@@ -313,37 +313,6 @@ pub async fn database_connection_from_config(
313313
.context("could not connect to the database")
314314
}
315315

316-
/// Reload templates on SIGHUP
317-
pub fn register_sighup(
318-
templates: &Templates,
319-
activity_tracker: &ActivityTracker,
320-
) -> anyhow::Result<()> {
321-
#[cfg(unix)]
322-
{
323-
let mut signal = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup())?;
324-
let templates = templates.clone();
325-
let activity_tracker = activity_tracker.clone();
326-
327-
tokio::spawn(async move {
328-
loop {
329-
if signal.recv().await.is_none() {
330-
// No more signals will be received, breaking
331-
break;
332-
};
333-
334-
info!("SIGHUP received, reloading templates & flushing activity tracker");
335-
336-
activity_tracker.flush().await;
337-
templates.clone().reload().await.unwrap_or_else(|err| {
338-
error!(?err, "Error while reloading templates");
339-
});
340-
}
341-
});
342-
}
343-
344-
Ok(())
345-
}
346-
347316
#[cfg(test)]
348317
mod tests {
349318
use rand::SeedableRng;

0 commit comments

Comments
 (0)