From 7908dc43ca2d68f5b6afc2a2e344faa1f1380c7d Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 7 May 2022 06:11:04 -0500 Subject: [PATCH] Introduce shutdown fairings. Also adds 'Client::terminate()' to run graceful shutdown in testing. Resolves #1707. --- core/lib/src/fairing/ad_hoc.rs | 31 +++++ core/lib/src/fairing/fairings.rs | 11 ++ core/lib/src/fairing/info_kind.rs | 9 +- core/lib/src/fairing/mod.rs | 86 ++++++++++-- core/lib/src/fs/temp_file.rs | 3 +- core/lib/src/local/asynchronous/client.rs | 10 +- core/lib/src/local/blocking/client.rs | 16 ++- core/lib/src/local/client.rs | 23 ++++ core/lib/src/rocket.rs | 2 +- core/lib/src/server.rs | 5 + core/lib/tests/shutdown-fairings.rs | 151 ++++++++++++++++++++++ site/guide/7-fairings.md | 36 ++++-- 12 files changed, 353 insertions(+), 30 deletions(-) create mode 100644 core/lib/tests/shutdown-fairings.rs diff --git a/core/lib/src/fairing/ad_hoc.rs b/core/lib/src/fairing/ad_hoc.rs index 915e1754..2add55a7 100644 --- a/core/lib/src/fairing/ad_hoc.rs +++ b/core/lib/src/fairing/ad_hoc.rs @@ -66,6 +66,9 @@ enum AdHocKind { /// sent to a client. Response(Box Fn(&'r Request<'_>, &'b mut Response<'r>) -> BoxFuture<'b, ()> + Send + Sync + 'static>), + + /// An ad-hoc **shutdown** fairing. Called on shutdown. + Shutdown(Once FnOnce(&'a Rocket) -> BoxFuture<'a, ()> + Send + 'static>), } impl AdHoc { @@ -181,6 +184,27 @@ impl AdHoc { AdHoc { name, kind: AdHocKind::Response(Box::new(f)) } } + /// Constructs an `AdHoc` shutdown fairing named `name`. The function `f` + /// will be called by Rocket when [shutdown is triggered]. + /// + /// [shutdown is triggered]: crate::config::Shutdown#triggers + /// + /// # Example + /// + /// ```rust + /// use rocket::fairing::AdHoc; + /// + /// // A fairing that prints a message just before launching. + /// let fairing = AdHoc::on_shutdown("Bye!", |_| Box::pin(async move { + /// println!("Rocket is on its way back!"); + /// })); + /// ``` + pub fn on_shutdown(name: &'static str, f: F) -> AdHoc + where F: for<'a> FnOnce(&'a Rocket) -> BoxFuture<'a, ()> + { + AdHoc { name, kind: AdHocKind::Shutdown(Once::new(Box::new(f))) } + } + /// Constructs an `AdHoc` launch fairing that extracts a configuration of /// type `T` from the configured provider and stores it in managed state. If /// extractions fails, pretty-prints the error message and aborts launch. @@ -229,6 +253,7 @@ impl Fairing for AdHoc { AdHocKind::Liftoff(_) => Kind::Liftoff, AdHocKind::Request(_) => Kind::Request, AdHocKind::Response(_) => Kind::Response, + AdHocKind::Shutdown(_) => Kind::Shutdown, }; Info { name: self.name, kind } @@ -258,4 +283,10 @@ impl Fairing for AdHoc { f(req, res).await } } + + async fn on_shutdown(&self, rocket: &Rocket) { + if let AdHocKind::Shutdown(ref f) = self.kind { + (f.take())(rocket).await + } + } } diff --git a/core/lib/src/fairing/fairings.rs b/core/lib/src/fairing/fairings.rs index 594afb5a..84685b27 100644 --- a/core/lib/src/fairing/fairings.rs +++ b/core/lib/src/fairing/fairings.rs @@ -19,6 +19,7 @@ pub struct Fairings { liftoff: Vec, request: Vec, response: Vec, + shutdown: Vec, } macro_rules! iter { @@ -46,6 +47,7 @@ impl Fairings { .chain(self.liftoff.iter()) .chain(self.request.iter()) .chain(self.response.iter()) + .chain(self.shutdown.iter()) } pub fn add(&mut self, fairing: Box) { @@ -90,6 +92,7 @@ impl Fairings { remove(i, &mut self.liftoff); remove(i, &mut self.request); remove(i, &mut self.response); + remove(i, &mut self.shutdown); } } @@ -99,6 +102,7 @@ impl Fairings { if this_info.kind.is(Kind::Liftoff) { self.liftoff.push(index); } if this_info.kind.is(Kind::Request) { self.request.push(index); } if this_info.kind.is(Kind::Response) { self.response.push(index); } + if this_info.kind.is(Kind::Shutdown) { self.shutdown.push(index); } } pub fn append(&mut self, others: &mut Fairings) { @@ -153,6 +157,12 @@ impl Fairings { } } + #[inline(always)] + pub async fn handle_shutdown(&self, rocket: &Rocket) { + let shutdown_futures = iter!(self.shutdown).map(|f| f.on_shutdown(rocket)); + futures::future::join_all(shutdown_futures).await; + } + pub fn audit(&self) -> Result<(), &[Info]> { match self.failures.is_empty() { true => Ok(()), @@ -184,6 +194,7 @@ impl std::fmt::Debug for Fairings { .field("liftoff", &debug_info(iter!(self.liftoff))) .field("request", &debug_info(iter!(self.request))) .field("response", &debug_info(iter!(self.response))) + .field("shutdown", &debug_info(iter!(self.shutdown))) .finish() } } diff --git a/core/lib/src/fairing/info_kind.rs b/core/lib/src/fairing/info_kind.rs index adaa129e..74ab3a48 100644 --- a/core/lib/src/fairing/info_kind.rs +++ b/core/lib/src/fairing/info_kind.rs @@ -18,7 +18,7 @@ use std::ops::BitOr; /// # let _unused_info = /// Info { /// name: "Example Fairing", -/// kind: Kind::Ignite | Kind::Liftoff | Kind::Request | Kind::Response +/// kind: Kind::Ignite | Kind::Liftoff | Kind::Request | Kind::Response | Kind::Shutdown /// } /// # ; /// ``` @@ -40,6 +40,7 @@ pub struct Info { /// * Liftoff /// * Request /// * Response +/// * Shutdown /// /// Two `Kind` structures can be `or`d together to represent a combination. For /// instance, to represent a fairing that is both an ignite and request fairing, @@ -66,9 +67,12 @@ impl Kind { /// `Kind` flag representing a request for a 'response' callback. pub const Response: Kind = Kind(1 << 3); + /// `Kind` flag representing a request for a 'shutdown' callback. + pub const Shutdown: Kind = Kind(1 << 4); + /// `Kind` flag representing a /// [singleton](crate::fairing::Fairing#singletons) fairing. - pub const Singleton: Kind = Kind(1 << 4); + pub const Singleton: Kind = Kind(1 << 5); /// Returns `true` if `self` is a superset of `other`. In other words, /// returns `true` if all of the kinds in `other` are also in `self`. @@ -141,6 +145,7 @@ impl std::fmt::Display for Kind { write("liftoff", Kind::Liftoff)?; write("request", Kind::Request)?; write("response", Kind::Response)?; + write("shutdown", Kind::Shutdown)?; write("singleton", Kind::Singleton) } } diff --git a/core/lib/src/fairing/mod.rs b/core/lib/src/fairing/mod.rs index 9dd5d623..67d200ba 100644 --- a/core/lib/src/fairing/mod.rs +++ b/core/lib/src/fairing/mod.rs @@ -101,14 +101,15 @@ pub type Result, E = Rocket> = std::result::ResultIgnite (`on_ignite`)** /// /// An ignite callback, represented by the [`Fairing::on_ignite()`] method, /// is called just prior to liftoff, during ignition. The state of the @@ -126,15 +127,20 @@ pub type Result, E = Rocket> = std::result::ResultLiftoff (`on_liftoff`)** /// /// A liftoff callback, represented by the [`Fairing::on_liftoff()`] method, /// is called immediately after a Rocket application has launched. At this /// point, Rocket has opened a socket for listening but has not yet begun /// accepting connections. A liftoff callback can inspect the `Rocket` -/// instance that has launched but not otherwise gracefully abort launch. +/// instance that has launched and even schedule a shutdown using +/// [`Shutdown::notify()`](crate::Shutdown::notify()) via +/// [`Rocket::shutdown()`]. /// -/// * **Request (`on_request`)** +/// Liftoff fairings are run concurrently; resolution of all fairings is +/// awaited before resuming request serving. +/// +/// * **Request (`on_request`)** /// /// A request callback, represented by the [`Fairing::on_request()`] method, /// is called just after a request is received, immediately after @@ -147,7 +153,7 @@ pub type Result, E = Rocket> = std::result::ResultResponse (`on_response`)** /// /// A response callback, represented by the [`Fairing::on_response()`] /// method, is called when a response is ready to be sent to the client. At @@ -162,6 +168,33 @@ pub type Result, E = Rocket> = std::result::ResultShutdown (`on_shutdown`)** +/// +/// A shutdown callback, represented by the [`Fairing::on_shutdown()`] +/// method, is called when [shutdown is triggered]. At this point, graceful +/// shutdown has commenced but not completed; no new requests are accepted +/// but the application may still be actively serving existing requests. +/// +/// Rocket guarantees, however, that all requests are completed or aborted +/// once [grace and mercy periods] have expired. This implies that a +/// shutdown fairing that (asynchronously) sleeps for `grace + mercy + ε` +/// seconds before executing any logic will execute said logic after all +/// requests have been processed or aborted. Note that such fairings may +/// wish to operate using the `Ok` return value of [`Rocket::launch()`] +/// instead. +/// +/// All registered shutdown fairings are run concurrently; resolution of all +/// fairings is awaited before resuming shutdown. Shutdown fairings do not +/// affect grace and mercy periods. In other words, any time consumed by +/// shutdown fairings is not added to grace and mercy periods. +/// +/// ***Note: Shutdown fairings are only run during testing if the `Client` +/// is terminated using [`Client::terminate()`].*** +/// +/// [shutdown is triggered]: crate::config::Shutdown#triggers +/// [grace and mercy periods]: crate::config::Shutdown#summary +/// [`Client::terminate()`]: crate::local::blocking::Client::terminate() +/// /// # Singletons /// /// In general, any number of instances of a given fairing type can be attached @@ -246,6 +279,11 @@ pub type Result, E = Rocket> = std::result::Result) { +/// /* ... */ +/// # unimplemented!() +/// } /// } /// ``` /// @@ -426,6 +464,8 @@ pub trait Fairing: Send + Sync + Any + 'static { /// The ignite callback. Returns `Ok` if ignition should proceed and `Err` /// if ignition and launch should be aborted. /// + /// See [Fairing Callbacks](#ignite) for complete semantics. + /// /// This method is called during ignition and if `Kind::Ignite` is in the /// `kind` field of the `Info` structure for this fairing. The `rocket` /// parameter is the `Rocket` instance that is currently being built for @@ -438,6 +478,8 @@ pub trait Fairing: Send + Sync + Any + 'static { /// The liftoff callback. /// + /// See [Fairing Callbacks](#liftoff) for complete semantics. + /// /// This method is called just after launching the application if /// `Kind::Liftoff` is in the `kind` field of the `Info` structure for this /// fairing. The `Rocket` parameter corresponds to the lauched application. @@ -449,6 +491,8 @@ pub trait Fairing: Send + Sync + Any + 'static { /// The request callback. /// + /// See [Fairing Callbacks](#request) for complete semantics. + /// /// This method is called when a new request is received if `Kind::Request` /// is in the `kind` field of the `Info` structure for this fairing. The /// `&mut Request` parameter is the incoming request, and the `&Data` @@ -461,6 +505,8 @@ pub trait Fairing: Send + Sync + Any + 'static { /// The response callback. /// + /// See [Fairing Callbacks](#response) for complete semantics. + /// /// This method is called when a response is ready to be issued to a client /// if `Kind::Response` is in the `kind` field of the `Info` structure for /// this fairing. The `&Request` parameter is the request that was routed, @@ -470,6 +516,21 @@ pub trait Fairing: Send + Sync + Any + 'static { /// /// The default implementation of this method does nothing. async fn on_response<'r>(&self, _req: &'r Request<'_>, _res: &mut Response<'r>) {} + + /// The shutdown callback. + /// + /// See [Fairing Callbacks](#shutdown) for complete semantics. + /// + /// This method is called when [shutdown is triggered] if `Kind::Shutdown` + /// is in the `kind` field of the `Info` structure for this fairing. The + /// `Rocket` parameter corresponds to the running application. + /// + /// [shutdown is triggered]: crate::config::Shutdown#triggers + /// + /// ## Default Implementation + /// + /// The default implementation of this method does nothing. + async fn on_shutdown(&self, _rocket: &Rocket) { } } #[crate::async_trait] @@ -498,4 +559,9 @@ impl Fairing for std::sync::Arc { async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) { (self as &T).on_response(req, res).await } + + #[inline] + async fn on_shutdown(&self, rocket: &Rocket) { + (self as &T).on_shutdown(rocket).await + } } diff --git a/core/lib/src/fs/temp_file.rs b/core/lib/src/fs/temp_file.rs index c3c96c2d..1c6df57e 100644 --- a/core/lib/src/fs/temp_file.rs +++ b/core/lib/src/fs/temp_file.rs @@ -451,7 +451,8 @@ impl<'v> TempFile<'v> { .unwrap_or(Limits::FILE); let temp_dir = req.rocket().config().temp_dir.relative(); - let file = task::spawn_blocking(move || NamedTempFile::new_in(temp_dir)).await; + let file = task::spawn_blocking(move || NamedTempFile::new_in(temp_dir)); + let file = file.await; let file = file.map_err(|_| io::Error::new(io::ErrorKind::Other, "spawn_block panic"))??; let (file, temp_path) = file.into_parts(); diff --git a/core/lib/src/local/asynchronous/client.rs b/core/lib/src/local/asynchronous/client.rs index a084dd9a..ecec4527 100644 --- a/core/lib/src/local/asynchronous/client.rs +++ b/core/lib/src/local/asynchronous/client.rs @@ -2,7 +2,7 @@ use std::fmt; use parking_lot::RwLock; -use crate::{Rocket, Phase, Orbit, Error}; +use crate::{Rocket, Phase, Orbit, Ignite, Error}; use crate::local::asynchronous::{LocalRequest, LocalResponse}; use crate::http::{Method, uri::Origin, private::cookie}; @@ -63,6 +63,7 @@ impl Client { } // WARNING: This is unstable! Do not use this method outside of Rocket! + // This is used by the `Client` doctests. #[doc(hidden)] pub fn _test(f: F) -> T where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send @@ -101,6 +102,13 @@ impl Client { LocalRequest::new(self, method, uri) } + pub(crate) async fn _terminate(self) -> Rocket { + let rocket = self.rocket; + rocket.shutdown().notify(); + rocket.fairings.handle_shutdown(&rocket).await; + rocket.into_ignite() + } + // Generates the public API methods, which call the private methods above. pub_client_impl!("use rocket::local::asynchronous::Client;" @async await); } diff --git a/core/lib/src/local/blocking/client.rs b/core/lib/src/local/blocking/client.rs index ea7bef9b..d3a8b0ef 100644 --- a/core/lib/src/local/blocking/client.rs +++ b/core/lib/src/local/blocking/client.rs @@ -1,7 +1,7 @@ use std::fmt; use std::cell::RefCell; -use crate::{Rocket, Phase, Orbit, Error}; +use crate::{Rocket, Phase, Orbit, Ignite, Error}; use crate::local::{asynchronous, blocking::{LocalRequest, LocalResponse}}; use crate::http::{Method, uri::Origin}; @@ -78,6 +78,15 @@ impl Client { self.inner()._with_raw_cookies(f) } + pub(crate) fn _terminate(mut self) -> Rocket { + let runtime = tokio::runtime::Builder::new_current_thread().build().unwrap(); + let runtime = self.runtime.replace(runtime); + let inner = self.inner.take().expect("invariant broken: self.inner is Some"); + let rocket = runtime.block_on(inner._terminate()); + runtime.shutdown_timeout(std::time::Duration::from_secs(1)); + rocket + } + #[inline(always)] fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c> where U: TryInto> + fmt::Display @@ -97,8 +106,9 @@ impl std::fmt::Debug for Client { impl Drop for Client { fn drop(&mut self) { - let client = self.inner.take(); - self.block_on(async { drop(client) }); + if let Some(client) = self.inner.take() { + self.block_on(async { drop(client) }); + } } } diff --git a/core/lib/src/local/client.rs b/core/lib/src/local/client.rs index d3aba814..5c2f92ee 100644 --- a/core/lib/src/local/client.rs +++ b/core/lib/src/local/client.rs @@ -95,6 +95,29 @@ macro_rules! pub_client_impl { Self::_new(rocket, false) $(.$suffix)? } + /// Terminates `Client` by initiating a graceful shutdown via + /// [`Shutdown::notify()`] and running shutdown fairings. + /// + /// This method _must_ be called on a `Client` if graceful shutdown is + /// required for testing as `Drop` _does not_ signal `Shutdown` nor run + /// shutdown fairings. Returns the instance of `Rocket` being managed by + /// this client after all shutdown fairings run to completion. + /// + /// [`Shutdown::notify()`]: crate::Shutdown::notify() + /// + /// ```rust,no_run + #[doc = $import] + /// + /// # fn f(client: Client) { + /// let client: Client = client; + /// let rocket = client.terminate(); + /// # } + /// ``` + #[inline(always)] + pub $($prefix)? fn terminate(self) -> Rocket { + Self::_terminate(self) $(.$suffix)? + } + #[doc(hidden)] pub $($prefix)? fn debug_with(routes: Vec) -> Result { let rocket = crate::custom(crate::Config::debug_default()); diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs index edaaff60..8acf8312 100644 --- a/core/lib/src/rocket.rs +++ b/core/lib/src/rocket.rs @@ -664,7 +664,7 @@ impl Rocket { } impl Rocket { - fn into_ignite(self) -> Rocket { + pub(crate) fn into_ignite(self) -> Rocket { Rocket(Igniting { router: self.0.router, fairings: self.0.fairings, diff --git a/core/lib/src/server.rs b/core/lib/src/server.rs index 2ca1bae5..45357cdc 100644 --- a/core/lib/src/server.rs +++ b/core/lib/src/server.rs @@ -502,10 +502,15 @@ impl Rocket { biased; _ = shutdown => { + // Run shutdown fairings. We compute `sleep()` for grace periods + // beforehand to ensure we don't add shutdown fairing completion + // time, which is arbitrary, to these periods. info!("Shutdown requested. Waiting for pending I/O..."); let grace_timer = sleep(Duration::from_secs(grace)); let mercy_timer = sleep(Duration::from_secs(grace + mercy)); let shutdown_timer = sleep(Duration::from_secs(grace + mercy + 1)); + rocket.fairings.handle_shutdown(&*rocket).await; + tokio::pin!(grace_timer, mercy_timer, shutdown_timer); tokio::select! { biased; diff --git a/core/lib/tests/shutdown-fairings.rs b/core/lib/tests/shutdown-fairings.rs new file mode 100644 index 00000000..edf0e230 --- /dev/null +++ b/core/lib/tests/shutdown-fairings.rs @@ -0,0 +1,151 @@ +#[macro_use] extern crate rocket; + +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +use rocket::fairing::AdHoc; + +// Want to test: +// +// * stalled connection + sleep in shutdown -> conn closed +// * stalled shutdown fairing stalls shutdown but not > grace + mercy +// - sleep < grace + mercy +// - sleep > grace + mercy + +#[derive(Default)] +struct Flags { + liftoff: AtomicBool, + shutdown: AtomicUsize +} + +#[test] +fn shutdown_fairing_runs() { + use rocket::local::blocking::Client; + + let rocket = rocket::build() + .manage(Flags::default()) + .attach(AdHoc::on_liftoff("Liftoff Flag", |rocket| Box::pin(async move { + let flags = rocket.state::().unwrap(); + flags.liftoff.store(true, Ordering::SeqCst); + }))) + .attach(AdHoc::on_shutdown("Shutdown Flag", |rocket| Box::pin(async move { + let flags = rocket.state::().unwrap(); + flags.shutdown.fetch_add(1, Ordering::SeqCst); + }))); + + let client = Client::debug(rocket).unwrap(); + let flags = client.rocket().state::().unwrap(); + assert!(flags.liftoff.load(Ordering::SeqCst)); + assert_eq!(0, flags.shutdown.load(Ordering::SeqCst)); + + let rocket = client.terminate(); + let flags = rocket.state::().unwrap(); + assert_eq!(1, flags.shutdown.load(Ordering::SeqCst)); +} + +#[async_test] +async fn async_shutdown_fairing_runs() { + use rocket::local::asynchronous::Client; + + let rocket = rocket::build() + .manage(Flags::default()) + .attach(AdHoc::on_liftoff("Liftoff Flag", |rocket| Box::pin(async move { + let flags = rocket.state::().unwrap(); + flags.liftoff.store(true, Ordering::SeqCst); + }))) + .attach(AdHoc::on_shutdown("Shutdown Flag", |rocket| Box::pin(async move { + let flags = rocket.state::().unwrap(); + flags.shutdown.fetch_add(1, Ordering::SeqCst); + }))); + + let client = Client::debug(rocket).await.unwrap(); + let flags = client.rocket().state::().unwrap(); + assert!(flags.liftoff.load(Ordering::SeqCst)); + assert_eq!(0, flags.shutdown.load(Ordering::SeqCst)); + + let rocket = client.terminate().await; + let flags = rocket.state::().unwrap(); + assert_eq!(1, flags.shutdown.load(Ordering::SeqCst)); +} + +#[async_test] +async fn multiple_shutdown_fairing_runs() { + use rocket::local::asynchronous::Client; + + let rocket = rocket::build() + .manage(Flags::default()) + .attach(AdHoc::on_shutdown("Shutdown Flag 1", |rocket| Box::pin(async move { + let flags = rocket.state::().unwrap(); + flags.shutdown.fetch_add(1, Ordering::SeqCst); + }))) + .attach(AdHoc::on_shutdown("Shutdown Flag 2", |rocket| Box::pin(async move { + let flags = rocket.state::().unwrap(); + flags.shutdown.fetch_add(1, Ordering::SeqCst); + }))); + + let client = Client::debug(rocket).await.unwrap(); + let flags = client.rocket().state::().unwrap(); + assert_eq!(0, flags.shutdown.load(Ordering::SeqCst)); + + let rocket = client.terminate().await; + let flags = rocket.state::().unwrap(); + assert_eq!(2, flags.shutdown.load(Ordering::SeqCst)); +} + +#[async_test] +async fn async_slow_shutdown_doesnt_elongate_grace() { + use rocket::local::asynchronous::Client; + + let mut config = rocket::Config::debug_default(); + config.shutdown.grace = 1; + config.shutdown.mercy = 1; + + let rocket = rocket::build() + .manage(Flags::default()) + .configure(config) + .attach(AdHoc::on_shutdown("Slow Shutdown", |rocket| Box::pin(async move { + tokio::time::sleep(std::time::Duration::from_secs(4)).await; + let flags = rocket.state::().unwrap(); + flags.shutdown.fetch_add(1, Ordering::SeqCst); + }))); + + let client = Client::debug(rocket).await.unwrap(); + let flags = client.rocket().state::().unwrap(); + assert_eq!(0, flags.shutdown.load(Ordering::SeqCst)); + + let start = std::time::Instant::now(); + let rocket = client.terminate().await; + let elapsed = start.elapsed(); + + let flags = rocket.state::().unwrap(); + assert!(elapsed > std::time::Duration::from_secs(2)); + assert!(elapsed < std::time::Duration::from_secs(5)); + assert_eq!(1, flags.shutdown.load(Ordering::SeqCst)); +} + +#[test] +fn background_tasks_dont_prevent_terminate() { + use rocket::local::blocking::Client; + + #[get("/")] + fn index() { + tokio::task::spawn(async { + tokio::time::sleep(std::time::Duration::from_secs(10)).await; + }); + + tokio::task::spawn_blocking(|| { + std::thread::sleep(std::time::Duration::from_secs(10)); + }); + } + + let mut config = rocket::Config::debug_default(); + config.shutdown.grace = 1; + config.shutdown.mercy = 1; + + let rocket = rocket::build().configure(config).mount("/", routes![index]); + + let client = Client::debug(rocket).unwrap(); + let response = client.get("/").dispatch(); + assert!(response.status().class().is_success()); + drop(response); + let _ = client.terminate(); +} diff --git a/site/guide/7-fairings.md b/site/guide/7-fairings.md index ca8f0ecb..f36e47d1 100644 --- a/site/guide/7-fairings.md +++ b/site/guide/7-fairings.md @@ -72,8 +72,8 @@ order in which fairings are attached may be significant. ### Callbacks -There are four events for which Rocket issues fairing callbacks. Each of these -events is described below: +There are five events for which Rocket issues fairing callbacks. Each of these +events is breifly described below and in details in the [`Fairing`] trait docs: * **Ignite (`on_ignite`)** @@ -106,7 +106,16 @@ events is described below: example, response fairings can also be used to inject headers into all outgoing responses. + * **Shutdown (`on_shutdown`)** + + A shutdown callback is called when [shutdown is triggered]. At this point, + graceful shutdown has commenced but not completed; no new requests are + accepted but the application may still be actively serving existing + requests. All registered shutdown fairings are run concurrently; resolution + of all fairings is awaited before resuming shutdown. + [ignition]: @api/rocket/struct.Rocket.html#method.ignite +[shutdown is triggered]: @api/rocket/config/struct.Shutdown.html#triggers ## Implementing @@ -115,8 +124,8 @@ Recall that a fairing is any type that implements the [`Fairing`] trait. A [`Info`] structure. This structure is used by Rocket to assign a name to the fairing and determine the set of callbacks the fairing is registering for. A `Fairing` can implement any of the available callbacks: [`on_ignite`], -[`on_liftoff`], [`on_request`], and [`on_response`]. Each callback has a default -implementation that does absolutely nothing. +[`on_liftoff`], [`on_request`], [`on_response`], and [`on_shutdown`]. Each +callback has a default implementation that does absolutely nothing. [`Info`]: @api/rocket/fairing/struct.Info.html [`info`]: @api/rocket/fairing/trait.Fairing.html#tymethod.info @@ -124,6 +133,7 @@ implementation that does absolutely nothing. [`on_liftoff`]: @api/rocket/fairing/trait.Fairing.html#method.on_liftoff [`on_request`]: @api/rocket/fairing/trait.Fairing.html#method.on_request [`on_response`]: @api/rocket/fairing/trait.Fairing.html#method.on_response +[`on_shutdown`]: @api/rocket/fairing/trait.Fairing.html#method.on_shutdown ### Requirements @@ -204,17 +214,16 @@ documentation](@api/rocket/fairing/trait.Fairing.html#example). ## Ad-Hoc Fairings -For simple occasions, implementing the `Fairing` trait can be cumbersome. This -is why Rocket provides the [`AdHoc`] type, which creates a fairing from a simple +For simpler cases, implementing the `Fairing` trait can be cumbersome. This is +why Rocket provides the [`AdHoc`] type, which creates a fairing from a simple function or closure. Using the `AdHoc` type is easy: simply call the -`on_ignite`, `on_liftoff`, `on_request`, or `on_response` constructors on -`AdHoc` to create an `AdHoc` structure from a function or closure. +`on_ignite`, `on_liftoff`, `on_request`, `on_response`, or `on_shutdown` +constructors on `AdHoc` to create a fairing from a function or closure. As an example, the code below creates a `Rocket` instance with two attached -ad-hoc fairings. The first, a liftoff fairing named "Liftoff Printer", simply -prints a message indicating that the application has launched. The second named -"Put Rewriter", a request fairing, rewrites the method of all requests to be -`PUT`. +ad-hoc fairings. The first, a liftoff fairing named "Liftoff Printer", prints a +message indicating that the application has launched. The second named "Put +Rewriter", a request fairing, rewrites the method of all requests to be `PUT`. ```rust use rocket::fairing::AdHoc; @@ -226,6 +235,9 @@ rocket::build() }))) .attach(AdHoc::on_request("Put Rewriter", |req, _| Box::pin(async move { req.set_method(Method::Put); + }))) + .attach(AdHoc::on_shutdown("Shutdown Printer", |_| Box::pin(async move { + println!("...shutdown has commenced!"); }))); ```