mirror of
https://github.com/rwf2/Rocket.git
synced 2025-01-20 00:19:05 +00:00
Introduce shutdown fairings.
Also adds 'Client::terminate()' to run graceful shutdown in testing. Resolves #1707.
This commit is contained in:
parent
1575f47753
commit
7908dc43ca
@ -66,6 +66,9 @@ enum AdHocKind {
|
||||
/// sent to a client.
|
||||
Response(Box<dyn for<'r, 'b> Fn(&'r Request<'_>, &'b mut Response<'r>)
|
||||
-> BoxFuture<'b, ()> + Send + Sync + 'static>),
|
||||
|
||||
/// An ad-hoc **shutdown** fairing. Called on shutdown.
|
||||
Shutdown(Once<dyn for<'a> FnOnce(&'a Rocket<Orbit>) -> 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<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc
|
||||
where F: for<'a> FnOnce(&'a Rocket<Orbit>) -> 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<Orbit>) {
|
||||
if let AdHocKind::Shutdown(ref f) = self.kind {
|
||||
(f.take())(rocket).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ pub struct Fairings {
|
||||
liftoff: Vec<usize>,
|
||||
request: Vec<usize>,
|
||||
response: Vec<usize>,
|
||||
shutdown: Vec<usize>,
|
||||
}
|
||||
|
||||
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<dyn Fairing>) {
|
||||
@ -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<Orbit>) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -101,14 +101,15 @@ pub type Result<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
|
||||
///
|
||||
/// ## Fairing Callbacks
|
||||
///
|
||||
/// There are four kinds of fairing callbacks: launch, liftoff, request, and
|
||||
/// response. A fairing can request any combination of these callbacks through
|
||||
/// the `kind` field of the [`Info`] structure returned from the `info` method.
|
||||
/// Rocket will only invoke the callbacks identified in the fairing's [`Kind`].
|
||||
/// There are five kinds of fairing callbacks: launch, liftoff, request,
|
||||
/// response, and shutdown. A fairing can request any combination of these
|
||||
/// callbacks through the `kind` field of the [`Info`] structure returned from
|
||||
/// the `info` method. Rocket will only invoke the callbacks identified in the
|
||||
/// fairing's [`Kind`].
|
||||
///
|
||||
/// The four callback kinds are as follows:
|
||||
/// The callback kinds are as follows:
|
||||
///
|
||||
/// * **Ignite (`on_ignite`)**
|
||||
/// * **<a name="ignite">Ignite</a> (`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<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
|
||||
/// ignite fairing returns `Err`, launch will be aborted. All ignite
|
||||
/// fairings are executed even if one or more signal a failure.
|
||||
///
|
||||
/// * **Liftoff (`on_liftoff`)**
|
||||
/// * **<a name="liftoff">Liftoff</a> (`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.
|
||||
///
|
||||
/// * **<a name="request">Request</a> (`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<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
|
||||
/// via response callbacks. Any modifications to a request are persisted and
|
||||
/// can potentially alter how a request is routed.
|
||||
///
|
||||
/// * **Response (`on_response`)**
|
||||
/// * **<a name="response">Response</a> (`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<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
|
||||
/// request. Additionally, Rocket will automatically strip the body for
|
||||
/// `HEAD` requests _after_ response fairings have run.
|
||||
///
|
||||
/// * **<a name="shutdown">Shutdown</a> (`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<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
|
||||
/// /* ... */
|
||||
/// # unimplemented!()
|
||||
/// }
|
||||
///
|
||||
/// async fn on_shutdown(&self, rocket: &Rocket<Orbit>) {
|
||||
/// /* ... */
|
||||
/// # 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<Orbit>) { }
|
||||
}
|
||||
|
||||
#[crate::async_trait]
|
||||
@ -498,4 +559,9 @@ impl<T: Fairing> Fairing for std::sync::Arc<T> {
|
||||
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<Orbit>) {
|
||||
(self as &T).on_shutdown(rocket).await
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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<T, F>(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<Ignite> {
|
||||
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);
|
||||
}
|
||||
|
@ -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<Ignite> {
|
||||
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<Origin<'u>> + 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) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Ignite> {
|
||||
Self::_terminate(self) $(.$suffix)?
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub $($prefix)? fn debug_with(routes: Vec<crate::Route>) -> Result<Self, Error> {
|
||||
let rocket = crate::custom(crate::Config::debug_default());
|
||||
|
@ -664,7 +664,7 @@ impl Rocket<Ignite> {
|
||||
}
|
||||
|
||||
impl Rocket<Orbit> {
|
||||
fn into_ignite(self) -> Rocket<Ignite> {
|
||||
pub(crate) fn into_ignite(self) -> Rocket<Ignite> {
|
||||
Rocket(Igniting {
|
||||
router: self.0.router,
|
||||
fairings: self.0.fairings,
|
||||
|
@ -502,10 +502,15 @@ impl Rocket<Orbit> {
|
||||
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;
|
||||
|
151
core/lib/tests/shutdown-fairings.rs
Normal file
151
core/lib/tests/shutdown-fairings.rs
Normal file
@ -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::<Flags>().unwrap();
|
||||
flags.liftoff.store(true, Ordering::SeqCst);
|
||||
})))
|
||||
.attach(AdHoc::on_shutdown("Shutdown Flag", |rocket| Box::pin(async move {
|
||||
let flags = rocket.state::<Flags>().unwrap();
|
||||
flags.shutdown.fetch_add(1, Ordering::SeqCst);
|
||||
})));
|
||||
|
||||
let client = Client::debug(rocket).unwrap();
|
||||
let flags = client.rocket().state::<Flags>().unwrap();
|
||||
assert!(flags.liftoff.load(Ordering::SeqCst));
|
||||
assert_eq!(0, flags.shutdown.load(Ordering::SeqCst));
|
||||
|
||||
let rocket = client.terminate();
|
||||
let flags = rocket.state::<Flags>().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::<Flags>().unwrap();
|
||||
flags.liftoff.store(true, Ordering::SeqCst);
|
||||
})))
|
||||
.attach(AdHoc::on_shutdown("Shutdown Flag", |rocket| Box::pin(async move {
|
||||
let flags = rocket.state::<Flags>().unwrap();
|
||||
flags.shutdown.fetch_add(1, Ordering::SeqCst);
|
||||
})));
|
||||
|
||||
let client = Client::debug(rocket).await.unwrap();
|
||||
let flags = client.rocket().state::<Flags>().unwrap();
|
||||
assert!(flags.liftoff.load(Ordering::SeqCst));
|
||||
assert_eq!(0, flags.shutdown.load(Ordering::SeqCst));
|
||||
|
||||
let rocket = client.terminate().await;
|
||||
let flags = rocket.state::<Flags>().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::<Flags>().unwrap();
|
||||
flags.shutdown.fetch_add(1, Ordering::SeqCst);
|
||||
})))
|
||||
.attach(AdHoc::on_shutdown("Shutdown Flag 2", |rocket| Box::pin(async move {
|
||||
let flags = rocket.state::<Flags>().unwrap();
|
||||
flags.shutdown.fetch_add(1, Ordering::SeqCst);
|
||||
})));
|
||||
|
||||
let client = Client::debug(rocket).await.unwrap();
|
||||
let flags = client.rocket().state::<Flags>().unwrap();
|
||||
assert_eq!(0, flags.shutdown.load(Ordering::SeqCst));
|
||||
|
||||
let rocket = client.terminate().await;
|
||||
let flags = rocket.state::<Flags>().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::<Flags>().unwrap();
|
||||
flags.shutdown.fetch_add(1, Ordering::SeqCst);
|
||||
})));
|
||||
|
||||
let client = Client::debug(rocket).await.unwrap();
|
||||
let flags = client.rocket().state::<Flags>().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::<Flags>().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();
|
||||
}
|
@ -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!");
|
||||
})));
|
||||
```
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user