Skip to content

Commit bfc38bf

Browse files
authored
fix(#201): address classifier gaps — run shutdown parity, extract layout, banner test (#205)
1 parent aac3ddd commit bfc38bf

5 files changed

Lines changed: 166 additions & 273 deletions

File tree

crates/rustapi-core/src/app/run.rs

Lines changed: 91 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ impl RustApi {
2323
}
2424
}
2525

26-
pub(super) fn print_hot_reload_banner(&self, addr: &str) {
26+
/// Returns `None` when hot-reload is disabled; otherwise whether a watcher was
27+
/// already active before this call updated `RUSTAPI_HOT_RELOAD`.
28+
pub(super) fn print_hot_reload_banner(&self, addr: &str) -> Option<bool> {
2729
if !self.hot_reload {
28-
return;
30+
return None;
2931
}
3032

3133
let is_under_watcher = std::env::var("RUSTAPI_HOT_RELOAD")
@@ -44,6 +46,13 @@ impl RustApi {
4446
}
4547

4648
tracing::info!(" Listening on http://{addr}");
49+
Some(is_under_watcher)
50+
}
51+
52+
async fn run_shutdown_hooks(hooks: Vec<crate::events::LifecycleHook>) {
53+
for hook in hooks {
54+
hook().await;
55+
}
4756
}
4857
pub(super) fn apply_status_page(&mut self) {
4958
if let Some(config) = &self.status_config {
@@ -236,20 +245,11 @@ impl RustApi {
236245
{
237246
self.prepare_for_serve(addr.as_ref()).await;
238247

239-
// Wrap the shutdown signal to run on_shutdown hooks after signal fires
240-
let shutdown_hooks = self.lifecycle_hooks.on_shutdown;
241-
let wrapped_signal = async move {
242-
signal.await;
243-
// Run on_shutdown hooks after the shutdown signal fires
244-
for hook in shutdown_hooks {
245-
hook().await;
246-
}
247-
};
248-
248+
let shutdown_hooks = std::mem::take(&mut self.lifecycle_hooks.on_shutdown);
249249
let server = Server::new(self.router, self.layers, self.interceptors);
250-
server
251-
.run_with_shutdown(addr.as_ref(), wrapped_signal)
252-
.await
250+
server.run_with_shutdown(addr.as_ref(), signal).await?;
251+
Self::run_shutdown_hooks(shutdown_hooks).await;
252+
Ok(())
253253
}
254254

255255
/// Enable HTTP/3 support with TLS certificates
@@ -265,16 +265,22 @@ impl RustApi {
265265
/// .run_http3("0.0.0.0:443", "cert.pem", "key.pem")
266266
/// .await
267267
/// ```
268+
/// Run HTTP/3 with TLS certificates and a graceful shutdown signal.
268269
#[cfg(feature = "http3")]
269-
pub async fn run_http3(
270+
pub async fn run_http3_with_shutdown<F>(
270271
mut self,
271272
config: crate::http3::Http3Config,
272-
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
273+
signal: F,
274+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>
275+
where
276+
F: std::future::Future<Output = ()> + Send + 'static,
277+
{
273278
use std::sync::Arc;
274279

275280
let addr = config.socket_addr();
276281
self.prepare_for_serve(&addr).await;
277282

283+
let shutdown_hooks = std::mem::take(&mut self.lifecycle_hooks.on_shutdown);
278284
let server = crate::http3::Http3Server::new(
279285
&config,
280286
Arc::new(self.router.clone()),
@@ -283,7 +289,18 @@ impl RustApi {
283289
)
284290
.await?;
285291

286-
server.run().await
292+
server.run_with_shutdown(signal).await?;
293+
Self::run_shutdown_hooks(shutdown_hooks).await;
294+
Ok(())
295+
}
296+
297+
#[cfg(feature = "http3")]
298+
pub async fn run_http3(
299+
self,
300+
config: crate::http3::Http3Config,
301+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
302+
self.run_http3_with_shutdown(config, std::future::pending())
303+
.await
287304
}
288305

289306
/// Run HTTP/3 server with self-signed certificate (development only)
@@ -299,15 +316,21 @@ impl RustApi {
299316
/// .run_http3_dev("0.0.0.0:8443")
300317
/// .await
301318
/// ```
319+
/// Run HTTP/3 (self-signed) with a graceful shutdown signal.
302320
#[cfg(feature = "http3-dev")]
303-
pub async fn run_http3_dev(
321+
pub async fn run_http3_dev_with_shutdown<F>(
304322
mut self,
305323
addr: &str,
306-
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
324+
signal: F,
325+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>
326+
where
327+
F: std::future::Future<Output = ()> + Send + 'static,
328+
{
307329
use std::sync::Arc;
308330

309331
self.prepare_for_serve(addr).await;
310332

333+
let shutdown_hooks = std::mem::take(&mut self.lifecycle_hooks.on_shutdown);
311334
let server = crate::http3::Http3Server::new_with_self_signed(
312335
addr,
313336
Arc::new(self.router.clone()),
@@ -316,7 +339,18 @@ impl RustApi {
316339
)
317340
.await?;
318341

319-
server.run().await
342+
server.run_with_shutdown(signal).await?;
343+
Self::run_shutdown_hooks(shutdown_hooks).await;
344+
Ok(())
345+
}
346+
347+
#[cfg(feature = "http3-dev")]
348+
pub async fn run_http3_dev(
349+
self,
350+
addr: &str,
351+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
352+
self.run_http3_dev_with_shutdown(addr, std::future::pending())
353+
.await
320354
}
321355

322356
/// Configure HTTP/3 support for `run_http3` and `run_dual_stack`.
@@ -349,11 +383,16 @@ impl RustApi {
349383
/// .run_dual_stack("0.0.0.0:8080")
350384
/// .await
351385
/// ```
386+
/// Run HTTP/1.1 and HTTP/3 together with a graceful shutdown signal.
352387
#[cfg(feature = "http3")]
353-
pub async fn run_dual_stack(
388+
pub async fn run_dual_stack_with_shutdown<F>(
354389
mut self,
355390
http_addr: &str,
356-
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
391+
signal: F,
392+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>
393+
where
394+
F: std::future::Future<Output = ()> + Send + 'static,
395+
{
357396
use std::sync::Arc;
358397

359398
let mut config = self
@@ -372,6 +411,7 @@ impl RustApi {
372411

373412
self.prepare_for_serve(&http_addr).await;
374413

414+
let shutdown_hooks = std::mem::take(&mut self.lifecycle_hooks.on_shutdown);
375415
let router = Arc::new(self.router);
376416
let layers = Arc::new(self.layers);
377417
let interceptors = Arc::new(self.interceptors);
@@ -387,11 +427,36 @@ impl RustApi {
387427
"Starting dual-stack HTTP/1.1 + HTTP/3 servers"
388428
);
389429

430+
let notify = std::sync::Arc::new(tokio::sync::Notify::new());
431+
let notify_for_signal = notify.clone();
432+
tokio::spawn(async move {
433+
signal.await;
434+
notify_for_signal.notify_waiters();
435+
});
436+
let wait_for_shutdown = {
437+
let notify = notify.clone();
438+
async move {
439+
notify.notified().await;
440+
}
441+
};
442+
let wait_for_shutdown_http3 = async move {
443+
notify.notified().await;
444+
};
445+
390446
tokio::try_join!(
391-
http1_server.run_with_shutdown(&http_addr, std::future::pending::<()>()),
392-
http3_server.run_with_shutdown(std::future::pending::<()>()),
447+
http1_server.run_with_shutdown(&http_addr, wait_for_shutdown),
448+
http3_server.run_with_shutdown(wait_for_shutdown_http3),
393449
)?;
394-
450+
Self::run_shutdown_hooks(shutdown_hooks).await;
395451
Ok(())
396452
}
453+
454+
#[cfg(feature = "http3")]
455+
pub async fn run_dual_stack(
456+
self,
457+
http_addr: &str,
458+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
459+
self.run_dual_stack_with_shutdown(http_addr, std::future::pending())
460+
.await
461+
}
397462
}

0 commit comments

Comments
 (0)