Use 'workers' value from 'Config::figment()'.

This commit also improves config pretty-printing and warning messages.
It also fixes an issue that resulted in config value deprecation
warnings not being emitted. The 'workers' value is now a 'usize', not a
'u16'; contrib pool sizes now default to 'workers * 2'.

Closes #1470.
This commit is contained in:
Sergio Benitez 2020-12-23 12:37:54 -08:00
parent 1f1f44f336
commit 9671115796
11 changed files with 131 additions and 38 deletions

View File

@ -131,7 +131,7 @@
//! Additionally, all configurations accept the following _optional_ keys: //! Additionally, all configurations accept the following _optional_ keys:
//! //!
//! * `pool_size` - the size of the pool, i.e., the number of connections to //! * `pool_size` - the size of the pool, i.e., the number of connections to
//! pool (defaults to the configured number of workers) //! pool (defaults to the configured number of workers * 2)
//! //!
//! Additional options may be required or supported by other adapters. //! Additional options may be required or supported by other adapters.
//! //!
@ -424,7 +424,7 @@ use self::r2d2::ManageConnection;
pub struct Config { pub struct Config {
/// Connection URL specified in the Rocket configuration. /// Connection URL specified in the Rocket configuration.
pub url: String, pub url: String,
/// Initial pool size. Defaults to the number of Rocket workers. /// Initial pool size. Defaults to the number of Rocket workers * 2.
pub pool_size: u32, pub pool_size: u32,
/// How long to wait, in seconds, for a new connection before timing out. /// How long to wait, in seconds, for a new connection before timing out.
/// Defaults to `5`. /// Defaults to `5`.
@ -462,7 +462,7 @@ impl Config {
/// ///
/// let config = Config::from("my_other_db", rocket).unwrap(); /// let config = Config::from("my_other_db", rocket).unwrap();
/// assert_eq!(config.url, "mysql://root:root@localhost/database"); /// assert_eq!(config.url, "mysql://root:root@localhost/database");
/// assert_eq!(config.pool_size, rocket.config().workers as u32); /// assert_eq!(config.pool_size, (rocket.config().workers * 2) as u32);
/// ///
/// let config = Config::from("unknown_db", rocket); /// let config = Config::from("unknown_db", rocket);
/// assert!(config.is_err()) /// assert!(config.is_err())
@ -477,7 +477,7 @@ impl Config {
let db_key = format!("databases.{}", db_name); let db_key = format!("databases.{}", db_name);
let key = |name: &str| format!("{}.{}", db_key, name); let key = |name: &str| format!("{}.{}", db_key, name);
Figment::from(rocket.figment()) Figment::from(rocket.figment())
.merge(Serialized::default(&key("pool_size"), rocket.config().workers)) .merge(Serialized::default(&key("pool_size"), rocket.config().workers * 2))
.merge(Serialized::default(&key("timeout"), 5)) .merge(Serialized::default(&key("timeout"), 5))
.extract_inner::<Self>(&db_key) .extract_inner::<Self>(&db_key)
} }

View File

@ -41,7 +41,7 @@ atomic = "0.5"
parking_lot = "0.11" parking_lot = "0.11"
ubyte = {version = "0.10", features = ["serde"] } ubyte = {version = "0.10", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
figment = { version = "0.9.2", features = ["toml", "env"] } figment = { version = "0.10", features = ["toml", "env"] }
rand = "0.7" rand = "0.7"
either = "1" either = "1"
@ -55,7 +55,7 @@ version_check = "0.9.1"
[dev-dependencies] [dev-dependencies]
bencher = "0.1" bencher = "0.1"
figment = { version = "0.9.2", features = ["test"] } figment = { version = "0.10", features = ["test"] }
[[bench]] [[bench]]
name = "format-routing" name = "format-routing"

View File

@ -25,7 +25,7 @@ use crate::data::Limits;
/// the appropriate of the two based on the selected profile. With the exception /// the appropriate of the two based on the selected profile. With the exception
/// of `log_level`, which is `normal` in `debug` and `critical` in `release`, /// of `log_level`, which is `normal` in `debug` and `critical` in `release`,
/// and `secret_key`, which is regenerated from a random value if not set in /// and `secret_key`, which is regenerated from a random value if not set in
/// "debug" mode only, all of the values are identical in either profile. /// "debug" mode only, all default values are identical in all profiles.
/// ///
/// # Provider Details /// # Provider Details
/// ///
@ -57,8 +57,8 @@ pub struct Config {
pub address: IpAddr, pub address: IpAddr,
/// Port to serve on. **(default: `8000`)** /// Port to serve on. **(default: `8000`)**
pub port: u16, pub port: u16,
/// Number of threads to use for executing futures. **(default: `cores * 2`)** /// Number of future-executing threads. **(default: `num cores`)**
pub workers: u16, pub workers: usize,
/// Keep-alive timeout in seconds; disabled when `0`. **(default: `5`)** /// Keep-alive timeout in seconds; disabled when `0`. **(default: `5`)**
pub keep_alive: u32, pub keep_alive: u32,
/// Max level to log. **(default: _debug_ `normal` / _release_ `critical`)** /// Max level to log. **(default: _debug_ `normal` / _release_ `critical`)**
@ -130,7 +130,7 @@ impl Config {
Config { Config {
address: Ipv4Addr::new(127, 0, 0, 1).into(), address: Ipv4Addr::new(127, 0, 0, 1).into(),
port: 8000, port: 8000,
workers: num_cpus::get() as u16 * 2, workers: num_cpus::get(),
keep_alive: 5, keep_alive: 5,
log_level: LogLevel::Normal, log_level: LogLevel::Normal,
cli_colors: true, cli_colors: true,
@ -215,16 +215,7 @@ impl Config {
/// let config = rocket::Config::from(figment); /// let config = rocket::Config::from(figment);
/// ``` /// ```
pub fn from<T: Provider>(provider: T) -> Self { pub fn from<T: Provider>(provider: T) -> Self {
// Check for now depreacted config values.
let figment = Figment::from(&provider); let figment = Figment::from(&provider);
for (key, replacement) in Self::DEPRECATED_KEYS {
if figment.find_value(key).is_ok() {
warn!("found value for deprecated config key `{}`", Paint::white(key));
if let Some(new_key) = replacement {
info_!("key has been by replaced by `{}`", Paint::white(new_key));
}
}
}
#[allow(unused_mut)] #[allow(unused_mut)]
let mut config = figment.extract::<Self>().unwrap_or_else(|e| { let mut config = figment.extract::<Self>().unwrap_or_else(|e| {
@ -233,16 +224,12 @@ impl Config {
}); });
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))] { #[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))] {
if config.secret_key.is_zero() { if !config.secret_key.is_provided() {
if figment.profile() != Self::DEBUG_PROFILE { if figment.profile() != Self::DEBUG_PROFILE {
crate::logger::try_init(LogLevel::Debug, true, false); crate::logger::try_init(LogLevel::Debug, true, false);
error!("secrets enabled in `release` without `secret_key`"); error!("secrets enabled in non-`debug` without `secret_key`");
info_!("disable `secrets` feature or configure a `secret_key`"); info_!("disable `secrets` feature or configure a `secret_key`");
panic!("aborting due to configuration error(s)") panic!("aborting due to configuration error(s)")
} else {
warn!("secrets enabled in `debug` without `secret_key`");
info_!("disable `secrets` feature or configure a `secret_key`");
info_!("this becomes a hard error in `release`");
} }
// in debug, generate a key for a bit more security // in debug, generate a key for a bit more security
@ -272,10 +259,11 @@ impl Config {
cfg!(feature = "tls") && self.tls.is_some() cfg!(feature = "tls") && self.tls.is_some()
} }
pub(crate) fn pretty_print(&self, profile: &Profile) { pub(crate) fn pretty_print(&self, figment: &Figment) {
use crate::logger::PaintExt; use crate::logger::PaintExt;
launch_info!("{}Configured for {}.", Paint::emoji("🔧 "), profile); launch_info!("{}Configured for {}.", Paint::emoji("🔧 "), figment.profile());
launch_info_!("address: {}", Paint::default(&self.address).bold()); launch_info_!("address: {}", Paint::default(&self.address).bold());
launch_info_!("port: {}", Paint::default(&self.port).bold()); launch_info_!("port: {}", Paint::default(&self.port).bold());
launch_info_!("workers: {}", Paint::default(self.workers).bold()); launch_info_!("workers: {}", Paint::default(self.workers).bold());
@ -295,6 +283,26 @@ impl Config {
true => launch_info_!("tls: {}", Paint::default("enabled").bold()), true => launch_info_!("tls: {}", Paint::default("enabled").bold()),
false => launch_info_!("tls: {}", Paint::default("disabled").bold()), false => launch_info_!("tls: {}", Paint::default("disabled").bold()),
} }
if !self.secret_key.is_provided() {
warn!("secrets enabled without a configured `secret_key`");
info_!("disable `secrets` feature or configure a `secret_key`");
info_!("this becomes a {} in non-debug profiles", Paint::red("hard error").bold());
}
// Check for now depreacted config values.
for (key, replacement) in Self::DEPRECATED_KEYS {
if let Some(md) = figment.find_metadata(key) {
warn!("found value for deprecated config key `{}`", Paint::white(key));
if let Some(ref source) = md.source {
info_!("in {} {}", Paint::white(source), md.name);
}
if let Some(new_key) = replacement {
info_!("key has been by replaced by `{}`", Paint::white(new_key));
}
}
}
} }
} }
@ -315,21 +323,62 @@ impl Provider for Config {
#[doc(hidden)] #[doc(hidden)]
pub fn pretty_print_error(error: figment::Error) { pub fn pretty_print_error(error: figment::Error) {
use figment::error::{Kind, OneOf};
crate::logger::try_init(LogLevel::Debug, true, false); crate::logger::try_init(LogLevel::Debug, true, false);
for e in error { for e in error {
error!("{}", e.kind); fn w<T: std::fmt::Display>(v: T) -> Paint<T> { Paint::white(v) }
match e.kind {
Kind::Message(msg) => error_!("{}", msg),
Kind::InvalidType(v, exp) => {
error_!("invalid type: found {}, expected {}", w(v), w(exp));
}
Kind::InvalidValue(v, exp) => {
error_!("invalid value {}, expected {}", w(v), w(exp));
},
Kind::InvalidLength(v, exp) => {
error_!("invalid length {}, expected {}", w(v), w(exp))
},
Kind::UnknownVariant(v, exp) => {
error_!("unknown variant: found `{}`, expected `{}`", w(v), w(OneOf(exp)))
}
Kind::UnknownField(v, exp) => {
error_!("unknown field: found `{}`, expected `{}`", w(v), w(OneOf(exp)))
}
Kind::MissingField(v) => {
error_!("missing field `{}`", w(v))
}
Kind::DuplicateField(v) => {
error_!("duplicate field `{}`", w(v))
}
Kind::ISizeOutOfRange(v) => {
error_!("signed integer `{}` is out of range", w(v))
}
Kind::USizeOutOfRange(v) => {
error_!("unsigned integer `{}` is out of range", w(v))
}
Kind::Unsupported(v) => {
error_!("unsupported type `{}`", w(v))
}
Kind::UnsupportedKey(a, e) => {
error_!("unsupported type `{}` for key: must be `{}`", w(a), w(e))
}
}
if let (Some(ref profile), Some(ref md)) = (&e.profile, &e.metadata) { if let (Some(ref profile), Some(ref md)) = (&e.profile, &e.metadata) {
if !e.path.is_empty() { if !e.path.is_empty() {
let key = md.interpolate(profile, &e.path); let key = md.interpolate(profile, &e.path);
info_!("for key {}", Paint::white(key)); info_!("for key {}", w(key));
} }
} }
if let Some(ref md) = e.metadata { if let Some(md) = e.metadata {
if let Some(ref source) = md.source { if let Some(source) = md.source {
info_!("in {} {}", Paint::white(source), md.name); info_!("in {} {}", w(source), md.name);
} else {
info_!("in {}", w(md.name));
} }
} }
} }

View File

@ -30,6 +30,17 @@
//! [`Rocket::figment()`]: crate::Rocket::figment() //! [`Rocket::figment()`]: crate::Rocket::figment()
//! [`Deserialize`]: serde::Deserialize //! [`Deserialize`]: serde::Deserialize
//! //!
//! ## Workers
//!
//! The `workers` parameter sets the number of threads used for parallel task
//! execution; there is no limit to the number of concurrent tasks. Due to a
//! limitation in upstream async executers, unlike other values, the `workers`
//! configuration value cannot be reconfigured or be configured from sources
//! other than those provided by [`Config::figment()`]. In other words, only the
//! values set by the `ROCKET_WORKERS` environment variable or in the `workers`
//! property of `Rocket.toml` will be considered - all other `workers` values
//! are ignored.
//!
//! ## Custom Providers //! ## Custom Providers
//! //!
//! A custom provider can be set via [`rocket::custom()`], which replaces calls to //! A custom provider can be set via [`rocket::custom()`], which replaces calls to

View File

@ -125,6 +125,25 @@ impl SecretKey {
pub fn is_zero(&self) -> bool { pub fn is_zero(&self) -> bool {
self.kind == Kind::Zero self.kind == Kind::Zero
} }
/// Returns `true` if `self` was not automatically generated and is not zero.
///
/// # Example
///
/// ```rust
/// use rocket::config::SecretKey;
///
/// let master = vec![0u8; 64];
/// let key = SecretKey::generate().unwrap();
/// assert!(!key.is_provided());
///
/// let master = vec![0u8; 64];
/// let key = SecretKey::from(&master);
/// assert!(!key.is_provided());
/// ```
pub fn is_provided(&self) -> bool {
self.kind == Kind::Provided
}
} }
#[doc(hidden)] #[doc(hidden)]

View File

@ -113,7 +113,7 @@ pub use self::info_kind::{Info, Kind};
/// An attach callback can arbitrarily modify the `Rocket` instance being /// An attach callback can arbitrarily modify the `Rocket` instance being
/// constructed. It returns `Ok` if it would like launching to proceed /// constructed. It returns `Ok` if it would like launching to proceed
/// nominally and `Err` otherwise. If an attach callback returns `Err`, /// nominally and `Err` otherwise. If an attach callback returns `Err`,
/// launch will be aborted. All attach callbacks are executed on `launch`, /// launch will be aborted. All attach callbacks are executed on `attach`,
/// even if one or more signal a failure. /// even if one or more signal a failure.
/// ///
/// * **Launch (`on_launch`)** /// * **Launch (`on_launch`)**

View File

@ -161,7 +161,6 @@ pub fn custom<T: figment::Provider>(provider: T) -> Rocket {
Rocket::custom(provider) Rocket::custom(provider)
} }
// TODO.async: More thoughtful plan for async tests
/// WARNING: This is unstable! Do not use this method outside of Rocket! /// WARNING: This is unstable! Do not use this method outside of Rocket!
#[doc(hidden)] #[doc(hidden)]
pub fn async_test<R>(fut: impl std::future::Future<Output = R> + Send) -> R { pub fn async_test<R>(fut: impl std::future::Future<Output = R> + Send) -> R {
@ -178,8 +177,12 @@ pub fn async_test<R>(fut: impl std::future::Future<Output = R> + Send) -> R {
/// WARNING: This is unstable! Do not use this method outside of Rocket! /// WARNING: This is unstable! Do not use this method outside of Rocket!
#[doc(hidden)] #[doc(hidden)]
pub fn async_main<R>(fut: impl std::future::Future<Output = R> + Send) -> R { pub fn async_main<R>(fut: impl std::future::Future<Output = R> + Send) -> R {
// FIXME: The `workers` value won't reflect swaps of `Rocket` in attach
// fairings with different config values, or values from non-Rocket configs.
// See tokio-rs/tokio#3329 for a necessary solution in `tokio`.
tokio::runtime::Builder::new() tokio::runtime::Builder::new()
.threaded_scheduler() .threaded_scheduler()
.core_threads(Config::from(Config::figment()).workers)
.thread_name("rocket-worker-thread") .thread_name("rocket-worker-thread")
.enable_all() .enable_all()
.build() .build()

View File

@ -83,7 +83,7 @@ impl Rocket {
pub fn custom<T: figment::Provider>(provider: T) -> Rocket { pub fn custom<T: figment::Provider>(provider: T) -> Rocket {
let (config, figment) = (Config::from(&provider), Figment::from(provider)); let (config, figment) = (Config::from(&provider), Figment::from(provider));
logger::try_init(config.log_level, config.cli_colors, false); logger::try_init(config.log_level, config.cli_colors, false);
config.pretty_print(figment.profile()); config.pretty_print(&figment);
let managed_state = Container::new(); let managed_state = Container::new();
let (shutdown_sender, shutdown_receiver) = mpsc::channel(1); let (shutdown_sender, shutdown_receiver) = mpsc::channel(1);

View File

@ -6,4 +6,4 @@ edition = "2018"
publish = false publish = false
[dependencies] [dependencies]
rocket = { path = "../../core/lib" } rocket = { path = "../../core/lib", features = ["secrets"] }

View File

@ -21,7 +21,7 @@ values:
|----------------|-----------------|-------------------------------------------------|-----------------------| |----------------|-----------------|-------------------------------------------------|-----------------------|
| `address` | `IpAddr` | IP address to serve on | `127.0.0.1` | | `address` | `IpAddr` | IP address to serve on | `127.0.0.1` |
| `port` | `u16` | Port to serve on. | `8000` | | `port` | `u16` | Port to serve on. | `8000` |
| `workers` | `u16` | Number of threads to use for executing futures. | cpu core count * 2 | | `workers` | `usize` | Number of threads to use for executing futures. | cpu core count |
| `keep_alive` | `u32` | Keep-alive timeout seconds; disabled when `0`. | `5` | | `keep_alive` | `u32` | Keep-alive timeout seconds; disabled when `0`. | `5` |
| `log_level` | `LogLevel` | Max level to log. (off/normal/debug/critical) | `normal`/`critical` | | `log_level` | `LogLevel` | Max level to log. (off/normal/debug/critical) | `normal`/`critical` |
| `cli_colors` | `bool` | Whether to use colors and emoji when logging. | `true` | | `cli_colors` | `bool` | Whether to use colors and emoji when logging. | `true` |
@ -110,6 +110,17 @@ file's directory.
! warning: Rocket's built-in TLS implements only TLS 1.2 and 1.3. As such, it ! warning: Rocket's built-in TLS implements only TLS 1.2 and 1.3. As such, it
may not be suitable for production use. may not be suitable for production use.
### Workers
The `workers` parameter sets the number of threads used for parallel task
execution; there is no limit to the number of concurrent tasks. Due to a
limitation in upstream async executers, unlike other values, the `workers`
configuration value cannot be reconfigured or be configured from sources other
than those provided by [`Config::figment()`], detailed below. In other words,
only the values set by the `ROCKET_WORKERS` environment variable or in the
`workers` property of `Rocket.toml` will be considered - all other `workers`
values are ignored.
## Default Provider ## Default Provider
Rocket's default configuration provider is [`Config::figment()`]; this is the Rocket's default configuration provider is [`Config::figment()`]; this is the

View File

@ -11,4 +11,4 @@ doc-comment = "0.3"
rocket_contrib = { path = "../../contrib/lib", features = ["json", "tera_templates", "diesel_sqlite_pool"] } rocket_contrib = { path = "../../contrib/lib", features = ["json", "tera_templates", "diesel_sqlite_pool"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
rand = "0.7" rand = "0.7"
figment = { version = "0.9.2", features = ["toml", "env"] } figment = { version = "0.10", features = ["toml", "env"] }