Introduce 'Singleton' fairings.

A singleton fairing is guaranteed to be the only instance of its type at
launch time. If more than one instance of a singleton fairing is
attached, only the last instance is retained.
This commit is contained in:
Sergio Benitez 2021-05-21 22:51:14 -07:00
parent 28ba04b47b
commit 267cb9396f
5 changed files with 204 additions and 39 deletions

View File

@ -1,3 +1,5 @@
use std::collections::HashSet;
use crate::{Rocket, Request, Response, Data, Build, Orbit}; use crate::{Rocket, Request, Response, Data, Build, Orbit};
use crate::fairing::{Fairing, Info, Kind}; use crate::fairing::{Fairing, Info, Kind};
use crate::log::PaintExt; use crate::log::PaintExt;
@ -6,12 +8,14 @@ use yansi::Paint;
#[derive(Default)] #[derive(Default)]
pub struct Fairings { pub struct Fairings {
// NOTE: This is a push-only vector due to the index-vectors below!
all_fairings: Vec<Box<dyn Fairing>>, all_fairings: Vec<Box<dyn Fairing>>,
// Ignite fairings that have failed.
failures: Vec<Info>, failures: Vec<Info>,
// Index into `attach` of last run attach fairing. // The number of ignite fairings from `self.ignite` we've run.
last_launch: usize, num_ignited: usize,
// The vectors below hold indices into `all_fairings`. // The vectors below hold indices into `all_fairings`.
launch: Vec<usize>, ignite: Vec<usize>,
liftoff: Vec<usize>, liftoff: Vec<usize>,
request: Vec<usize>, request: Vec<usize>,
response: Vec<usize>, response: Vec<usize>,
@ -19,8 +23,15 @@ pub struct Fairings {
macro_rules! iter { macro_rules! iter {
($_self:ident . $kind:ident) => ({ ($_self:ident . $kind:ident) => ({
iter!($_self, $_self.$kind.iter()).map(|v| v.1)
});
($_self:ident, $indices:expr) => ({
let all_fairings = &$_self.all_fairings; let all_fairings = &$_self.all_fairings;
$_self.$kind.iter().filter_map(move |i| all_fairings.get(*i).map(|f| &**f)) $indices.filter_map(move |i| {
debug_assert!(all_fairings.get(*i).is_some());
let f = all_fairings.get(*i).map(|f| &**f)?;
Some((*i, f))
})
}) })
} }
@ -30,17 +41,64 @@ impl Fairings {
Fairings::default() Fairings::default()
} }
pub fn add(&mut self, fairing: Box<dyn Fairing>) -> &dyn Fairing { pub fn active(&self) -> impl Iterator<Item = &usize> {
let kind = fairing.info().kind; self.ignite.iter()
.chain(self.liftoff.iter())
.chain(self.request.iter())
.chain(self.response.iter())
}
pub fn add(&mut self, fairing: Box<dyn Fairing>) {
let this = &fairing;
let this_info = this.info();
if this_info.kind.is(Kind::Singleton) {
// If we already ran a duplicate on ignite, then fail immediately.
// There is no way to uphold the "only run last singleton" promise.
//
// How can this happen? Like this:
// 1. Attach A (singleton).
// 2. Attach B (any fairing).
// 3. Ignite.
// 4. A executes on_ignite.
// 5. B executes on_ignite, attaches another A.
// 6. --- (A would run if not for this code)
let ignite_dup = iter!(self.ignite).position(|f| f.type_id() == this.type_id());
if let Some(dup_ignite_index) = ignite_dup {
if dup_ignite_index < self.num_ignited {
self.failures.push(this_info);
return;
}
}
// Finds `k` in `from` and removes it if it's there.
let remove = |k: usize, from: &mut Vec<usize>| {
if let Ok(j) = from.binary_search(&k) {
from.remove(j);
}
};
// Collect all of the active duplicates.
let mut dups: Vec<usize> = iter!(self, self.active())
.filter(|(_, f)| f.type_id() == this.type_id())
.map(|(i, _)| i)
.collect();
// Reverse the dup indices so `remove` is stable given shifts.
dups.sort(); dups.dedup(); dups.reverse();
for i in dups {
remove(i, &mut self.ignite);
remove(i, &mut self.liftoff);
remove(i, &mut self.request);
remove(i, &mut self.response);
}
}
let index = self.all_fairings.len(); let index = self.all_fairings.len();
self.all_fairings.push(fairing); self.all_fairings.push(fairing);
if this_info.kind.is(Kind::Ignite) { self.ignite.push(index); }
if kind.is(Kind::Ignite) { self.launch.push(index); } if this_info.kind.is(Kind::Liftoff) { self.liftoff.push(index); }
if kind.is(Kind::Liftoff) { self.liftoff.push(index); } if this_info.kind.is(Kind::Request) { self.request.push(index); }
if kind.is(Kind::Request) { self.request.push(index); } if this_info.kind.is(Kind::Response) { self.response.push(index); }
if kind.is(Kind::Response) { self.response.push(index); }
&*self.all_fairings[index]
} }
pub fn append(&mut self, others: &mut Fairings) { pub fn append(&mut self, others: &mut Fairings) {
@ -50,10 +108,10 @@ impl Fairings {
} }
pub async fn handle_ignite(mut rocket: Rocket<Build>) -> Rocket<Build> { pub async fn handle_ignite(mut rocket: Rocket<Build>) -> Rocket<Build> {
while rocket.fairings.last_launch < rocket.fairings.launch.len() { while rocket.fairings.num_ignited < rocket.fairings.ignite.len() {
// We're going to move `rocket` while borrowing `fairings`... // We're going to move `rocket` while borrowing `fairings`...
let mut fairings = std::mem::replace(&mut rocket.fairings, Fairings::new()); let mut fairings = std::mem::replace(&mut rocket.fairings, Fairings::new());
for fairing in iter!(fairings.launch).skip(fairings.last_launch) { for fairing in iter!(fairings.ignite).skip(fairings.num_ignited) {
let info = fairing.info(); let info = fairing.info();
rocket = match fairing.on_ignite(rocket).await { rocket = match fairing.on_ignite(rocket).await {
Ok(rocket) => rocket, Ok(rocket) => rocket,
@ -63,10 +121,10 @@ impl Fairings {
} }
}; };
fairings.last_launch += 1; fairings.num_ignited += 1;
} }
// Note that `rocket.fairings` may now be non-empty since launch // Note that `rocket.fairings` may now be non-empty since ignite
// fairings could have added more fairings! Move them to the end. // fairings could have added more fairings! Move them to the end.
fairings.append(&mut rocket.fairings); fairings.append(&mut rocket.fairings);
rocket.fairings = fairings; rocket.fairings = fairings;
@ -89,9 +147,9 @@ impl Fairings {
} }
#[inline(always)] #[inline(always)]
pub async fn handle_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) { pub async fn handle_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
for fairing in iter!(self.response) { for fairing in iter!(self.response) {
fairing.on_response(request, response).await; fairing.on_response(req, res).await;
} }
} }
@ -103,13 +161,14 @@ impl Fairings {
} }
pub fn pretty_print(&self) { pub fn pretty_print(&self) {
if !self.all_fairings.is_empty() { let active_fairings = self.active().collect::<HashSet<_>>();
if !active_fairings.is_empty() {
launch_info!("{}{}:", Paint::emoji("📡 "), Paint::magenta("Fairings")); launch_info!("{}{}:", Paint::emoji("📡 "), Paint::magenta("Fairings"));
}
for fairing in &self.all_fairings { for (_, fairing) in iter!(self, active_fairings.into_iter()) {
launch_info_!("{} ({})", Paint::default(fairing.info().name).bold(), launch_info_!("{} ({})", Paint::default(fairing.info().name).bold(),
Paint::blue(fairing.info().kind).bold()); Paint::blue(fairing.info().kind).bold());
}
} }
} }
} }
@ -121,7 +180,7 @@ impl std::fmt::Debug for Fairings {
} }
f.debug_struct("Fairings") f.debug_struct("Fairings")
.field("launch", &debug_info(iter!(self.launch))) .field("launch", &debug_info(iter!(self.ignite)))
.field("liftoff", &debug_info(iter!(self.liftoff))) .field("liftoff", &debug_info(iter!(self.liftoff)))
.field("request", &debug_info(iter!(self.request))) .field("request", &debug_info(iter!(self.request)))
.field("response", &debug_info(iter!(self.response))) .field("response", &debug_info(iter!(self.response)))

View File

@ -27,7 +27,7 @@ pub struct Info {
/// The name of the fairing. /// The name of the fairing.
pub name: &'static str, pub name: &'static str,
/// A set representing the callbacks the fairing wishes to receive. /// A set representing the callbacks the fairing wishes to receive.
pub kind: Kind pub kind: Kind,
} }
/// A bitset representing the kinds of callbacks a /// A bitset representing the kinds of callbacks a
@ -45,6 +45,10 @@ pub struct Info {
/// instance, to represent a fairing that is both an ignite and request fairing, /// instance, to represent a fairing that is both an ignite and request fairing,
/// use `Kind::Ignite | Kind::Request`. Similarly, to represent a fairing that /// use `Kind::Ignite | Kind::Request`. Similarly, to represent a fairing that
/// is only an ignite fairing, use `Kind::Ignite`. /// is only an ignite fairing, use `Kind::Ignite`.
///
/// Additionally, a fairing can request to be treated as a
/// [singleton](crate::fairing::Fairing#singletons) by specifying the
/// `Singleton` kind.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Kind(usize); pub struct Kind(usize);
@ -62,6 +66,10 @@ impl Kind {
/// `Kind` flag representing a request for a 'response' callback. /// `Kind` flag representing a request for a 'response' callback.
pub const Response: Kind = Kind(1 << 3); pub const Response: Kind = Kind(1 << 3);
/// `Kind` flag representing a
/// [singleton](crate::fairing::Fairing#singletons) fairing.
pub const Singleton: Kind = Kind(1 << 4);
/// Returns `true` if `self` is a superset of `other`. In other words, /// 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`. /// returns `true` if all of the kinds in `other` are also in `self`.
/// ///
@ -132,6 +140,7 @@ impl std::fmt::Display for Kind {
write("ignite", Kind::Ignite)?; write("ignite", Kind::Ignite)?;
write("liftoff", Kind::Liftoff)?; write("liftoff", Kind::Liftoff)?;
write("request", Kind::Request)?; write("request", Kind::Request)?;
write("response", Kind::Response) write("response", Kind::Response)?;
write("singleton", Kind::Singleton)
} }
} }

View File

@ -38,15 +38,19 @@
//! ## Ordering //! ## Ordering
//! //!
//! `Fairing`s are executed in the order in which they are attached: the first //! `Fairing`s are executed in the order in which they are attached: the first
//! attached fairing has its callbacks executed before all others. Because //! attached fairing has its callbacks executed before all others. A fairing can
//! fairing callbacks may not be commutative, the order in which fairings are //! be attached any number of times. Except for [singleton
//! attached may be significant. Because of this, it is important to communicate //! fairings](Fairing#singletons), all attached instances are polled at runtime.
//! to the user every consequence of a fairing. //! Fairing callbacks may not be commutative; the order in which fairings are
//! attached may be significant. It is thus important to communicate specific
//! fairing functionality clearly.
//! //!
//! Furthermore, a `Fairing` should take care to act locally so that the actions //! Furthermore, a `Fairing` should take care to act locally so that the actions
//! of other `Fairings` are not jeopardized. For instance, unless it is made //! of other `Fairings` are not jeopardized. For instance, unless it is made
//! abundantly clear, a fairing should not rewrite every request. //! abundantly clear, a fairing should not rewrite every request.
use std::any::Any;
use crate::{Rocket, Request, Response, Data, Build, Orbit}; use crate::{Rocket, Request, Response, Data, Build, Orbit};
mod fairings; mod fairings;
@ -99,8 +103,8 @@ pub type Result<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
/// ///
/// There are four kinds of fairing callbacks: launch, liftoff, request, and /// There are four kinds of fairing callbacks: launch, liftoff, request, and
/// response. A fairing can request any combination of these callbacks through /// response. A fairing can request any combination of these callbacks through
/// the `kind` field of the `Info` structure returned from the `info` method. /// the `kind` field of the [`Info`] structure returned from the `info` method.
/// Rocket will only invoke the callbacks set in the `kind` field. /// Rocket will only invoke the callbacks identified in the fairing's [`Kind`].
/// ///
/// The four callback kinds are as follows: /// The four callback kinds are as follows:
/// ///
@ -158,6 +162,16 @@ pub type Result<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
/// request. Additionally, Rocket will automatically strip the body for /// request. Additionally, Rocket will automatically strip the body for
/// `HEAD` requests _after_ response fairings have run. /// `HEAD` requests _after_ response fairings have run.
/// ///
/// # Singletons
///
/// In general, any number of instances of a given fairing type can be attached
/// to one instance of `Rocket`. If this is not desired, a fairing can request
/// to be a singleton by specifying [`Kind::Singleton`]. Only the _last_
/// attached instance of a singleton will be preserved at ignite-time. That is,
/// an attached singleton instance will replace any previously attached
/// instance. The [`Shield`](crate::shield::Shield) fairing is an example of a
/// singleton fairing.
///
/// # Implementing /// # Implementing
/// ///
/// A `Fairing` implementation has one required method: [`info`]. A `Fairing` /// A `Fairing` implementation has one required method: [`info`]. A `Fairing`
@ -373,7 +387,7 @@ pub type Result<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
/// ///
/// [request-local state]: https://rocket.rs/master/guide/state/#request-local-state /// [request-local state]: https://rocket.rs/master/guide/state/#request-local-state
#[crate::async_trait] #[crate::async_trait]
pub trait Fairing: Send + Sync + 'static { pub trait Fairing: Send + Sync + Any + 'static {
/// Returns an [`Info`] structure containing the `name` and [`Kind`] of this /// Returns an [`Info`] structure containing the `name` and [`Kind`] of this
/// fairing. The `name` can be any arbitrary string. `Kind` must be an `or`d /// fairing. The `name` can be any arbitrary string. `Kind` must be an `or`d
/// set of `Kind` variants. /// set of `Kind` variants.

View File

@ -0,0 +1,82 @@
use rocket::{Rocket, Build, Config};
use rocket::fairing::{self, Fairing, Info, Kind};
use rocket::error::ErrorKind;
struct Singleton(Kind, Kind, bool);
#[rocket::async_trait]
impl Fairing for Singleton {
fn info(&self) -> Info {
Info {
name: "Singleton",
kind: self.0
}
}
async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
if self.2 {
Ok(rocket.attach(Singleton(self.1, self.1, false)))
} else {
Ok(rocket)
}
}
}
// Have => two `Singleton`s. This is okay; we keep the latter.
#[rocket::async_test]
async fn recursive_singleton_ok() {
let result = rocket::custom(Config::debug_default())
.attach(Singleton(Kind::Ignite | Kind::Singleton, Kind::Singleton, false))
.attach(Singleton(Kind::Ignite | Kind::Singleton, Kind::Singleton, false))
.ignite()
.await;
assert!(result.is_ok(), "{:?}", result);
let result = rocket::custom(Config::debug_default())
.attach(Singleton(Kind::Ignite | Kind::Singleton, Kind::Singleton, false))
.attach(Singleton(Kind::Ignite | Kind::Singleton, Kind::Singleton, false))
.attach(Singleton(Kind::Ignite | Kind::Singleton, Kind::Singleton, false))
.attach(Singleton(Kind::Ignite | Kind::Singleton, Kind::Singleton, false))
.ignite()
.await;
assert!(result.is_ok(), "{:?}", result);
}
// Have a `Singleton` add itself `on_ignite()`. Since it already ran, the one it
// adds can't be unique, so ensure we error in this case.
#[rocket::async_test]
async fn recursive_singleton_bad() {
#[track_caller]
fn assert_err(error: rocket::Error) {
if let ErrorKind::FailedFairings(v) = error.kind() {
assert_eq!(v.len(), 1);
assert_eq!(v[0].name, "Singleton");
} else {
panic!("unexpected error: {:?}", error);
}
}
let result = rocket::custom(Config::debug_default())
.attach(Singleton(Kind::Ignite | Kind::Singleton, Kind::Ignite | Kind::Singleton, true))
.ignite()
.await;
assert_err(result.unwrap_err());
let result = rocket::custom(Config::debug_default())
.attach(Singleton(Kind::Ignite | Kind::Singleton, Kind::Singleton, true))
.ignite()
.await;
assert_err(result.unwrap_err());
let result = rocket::custom(Config::debug_default())
.attach(Singleton(Kind::Ignite, Kind::Singleton, true))
.ignite()
.await;
assert_err(result.unwrap_err());
}

View File

@ -54,21 +54,22 @@ example, the following snippet attached two fairings, `req_fairing` and
fn rocket() -> _ { fn rocket() -> _ {
# let req_fairing = rocket::fairing::AdHoc::on_request("example", |_, _| Box::pin(async {})); # let req_fairing = rocket::fairing::AdHoc::on_request("example", |_, _| Box::pin(async {}));
# let res_fairing = rocket::fairing::AdHoc::on_response("example", |_, _| Box::pin(async {})); # let res_fairing = rocket::fairing::AdHoc::on_response("example", |_, _| Box::pin(async {}));
rocket::build() rocket::build()
.attach(req_fairing) .attach(req_fairing)
.attach(res_fairing) .attach(res_fairing)
} }
``` ```
Fairings are executed in the order in which they are attached: the first
attached fairing has its callbacks executed before all others. A fairing can be
attached any number of times. Except for [singleton fairings], all attached
instances are polled at runtime. Fairing callbacks may not be commutative; the
order in which fairings are attached may be significant.
[singleton fairings]: @api/rocket/fairing/trait.Fairing.html#singletons
[`attach`]: @api/rocket/struct.Rocket.html#method.attach [`attach`]: @api/rocket/struct.Rocket.html#method.attach
[`Rocket`]: @api/rocket/struct.Rocket.html [`Rocket`]: @api/rocket/struct.Rocket.html
Fairings are executed in the order in which they are attached: the first
attached fairing has its callbacks executed before all others. Because fairing
callbacks may not be commutative, the order in which fairings are attached may
be significant.
### Callbacks ### Callbacks
There are four events for which Rocket issues fairing callbacks. Each of these There are four events for which Rocket issues fairing callbacks. Each of these