Skip to content

Use a single connection pool across the whole application #843

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 27, 2020
66 changes: 29 additions & 37 deletions src/bin/cratesfyi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use std::env;
use std::path::PathBuf;
use std::sync::Arc;

use cratesfyi::db::{self, add_path_into_database, connect_db};
use cratesfyi::db::{self, add_path_into_database, Pool};
use cratesfyi::utils::{add_crate_to_queue, remove_crate_priority, set_crate_priority};
use cratesfyi::Server;
use cratesfyi::{DocBuilder, DocBuilderOptions, RustwideBuilder};
use cratesfyi::{Config, DocBuilder, DocBuilderOptions, RustwideBuilder, Server};
use failure::Error;
use postgres::Connection;
use structopt::StructOpt;

pub fn main() -> Result<(), Error> {
Expand Down Expand Up @@ -82,25 +82,26 @@ enum CommandLine {

impl CommandLine {
pub fn handle_args(self) -> Result<(), Error> {
let config = Arc::new(cratesfyi::Config::from_env()?);
let config = Arc::new(Config::from_env()?);
let pool = Pool::new(&config)?;

match self {
Self::Build(build) => build.handle_args(),
Self::Build(build) => build.handle_args(pool),
Self::StartWebServer {
socket_addr,
reload_templates,
} => {
Server::start(Some(&socket_addr), reload_templates, config)?;
Server::start(Some(&socket_addr), reload_templates, pool, config)?;
}
Self::Daemon { foreground } => {
if foreground {
log::warn!("--foreground was passed, but there is no need for it anymore");
}

cratesfyi::utils::start_daemon(config)?;
cratesfyi::utils::start_daemon(config, pool)?;
}
Self::Database { subcommand } => subcommand.handle_args(),
Self::Queue { subcommand } => subcommand.handle_args(),
Self::Database { subcommand } => subcommand.handle_args(&*pool.get()?),
Self::Queue { subcommand } => subcommand.handle_args(&*pool.get()?),
}

Ok(())
Expand Down Expand Up @@ -135,19 +136,18 @@ enum QueueSubcommand {
}

impl QueueSubcommand {
pub fn handle_args(self) {
pub fn handle_args(self, conn: &Connection) {
match self {
Self::Add {
crate_name,
crate_version,
build_priority,
} => {
let conn = connect_db().expect("Could not connect to database");
add_crate_to_queue(&conn, &crate_name, &crate_version, build_priority)
.expect("Could not add crate to queue");
}

Self::DefaultPriority { subcommand } => subcommand.handle_args(),
Self::DefaultPriority { subcommand } => subcommand.handle_args(conn),
}
}
}
Expand All @@ -172,18 +172,14 @@ enum PrioritySubcommand {
}

impl PrioritySubcommand {
pub fn handle_args(self) {
pub fn handle_args(self, conn: &Connection) {
match self {
Self::Set { pattern, priority } => {
let conn = connect_db().expect("Could not connect to the database");

set_crate_priority(&conn, &pattern, priority)
.expect("Could not set pattern's priority");
}

Self::Remove { pattern } => {
let conn = connect_db().expect("Could not connect to the database");

if let Some(priority) = remove_crate_priority(&conn, &pattern)
.expect("Could not remove pattern's priority")
{
Expand Down Expand Up @@ -237,7 +233,7 @@ struct Build {
}

impl Build {
pub fn handle_args(self) {
pub fn handle_args(self, pool: Pool) {
let docbuilder = {
let mut doc_options = DocBuilderOptions::from_prefix(self.prefix);

Expand All @@ -253,10 +249,10 @@ impl Build {
.check_paths()
.expect("The given paths were invalid");

DocBuilder::new(doc_options)
DocBuilder::new(doc_options, pool.clone())
};

self.subcommand.handle_args(docbuilder);
self.subcommand.handle_args(docbuilder, pool);
}
}

Expand Down Expand Up @@ -304,12 +300,12 @@ enum BuildSubcommand {
}

impl BuildSubcommand {
pub fn handle_args(self, mut docbuilder: DocBuilder) {
pub fn handle_args(self, mut docbuilder: DocBuilder, pool: cratesfyi::db::Pool) {
match self {
Self::World => {
docbuilder.load_cache().expect("Failed to load cache");

let mut builder = RustwideBuilder::init().unwrap();
let mut builder = RustwideBuilder::init(pool).unwrap();
builder
.build_world(&mut docbuilder)
.expect("Failed to build world");
Expand All @@ -323,7 +319,8 @@ impl BuildSubcommand {
local,
} => {
docbuilder.load_cache().expect("Failed to load cache");
let mut builder = RustwideBuilder::init().expect("failed to initialize rustwide");
let mut builder =
RustwideBuilder::init(pool).expect("failed to initialize rustwide");

if let Some(path) = local {
builder
Expand All @@ -345,7 +342,7 @@ impl BuildSubcommand {

Self::UpdateToolchain { only_first_time } => {
if only_first_time {
let conn = db::connect_db().unwrap();
let conn = pool.get().expect("failed to get a database connection");
let res = conn
.query("SELECT * FROM config WHERE name = 'rustc_version';", &[])
.unwrap();
Expand All @@ -356,14 +353,14 @@ impl BuildSubcommand {
}
}

let mut builder = RustwideBuilder::init().unwrap();
let mut builder = RustwideBuilder::init(pool).unwrap();
builder
.update_toolchain()
.expect("failed to update toolchain");
}

Self::AddEssentialFiles => {
let mut builder = RustwideBuilder::init().unwrap();
let mut builder = RustwideBuilder::init(pool).unwrap();
builder
.add_essential_files()
.expect("failed to add essential files");
Expand All @@ -378,7 +375,7 @@ impl BuildSubcommand {

#[derive(Debug, Clone, PartialEq, Eq, StructOpt)]
enum DatabaseSubcommand {
/// Run database migrations
/// Run database migration
Migrate {
/// The database version to migrate to
#[structopt(name = "VERSION")]
Expand Down Expand Up @@ -415,33 +412,30 @@ enum DatabaseSubcommand {
}

impl DatabaseSubcommand {
pub fn handle_args(self) {
pub fn handle_args(self, conn: &Connection) {
match self {
Self::Migrate { version } => {
let conn = connect_db().expect("failed to connect to the database");
db::migrate(version, &conn).expect("Failed to run database migrations");
}

Self::UpdateGithubFields => {
cratesfyi::utils::github_updater().expect("Failed to update github fields");
cratesfyi::utils::github_updater(&conn).expect("Failed to update github fields");
}

Self::AddDirectory { directory, prefix } => {
let conn = db::connect_db().expect("failed to connect to the database");
add_path_into_database(&conn, &prefix, directory)
.expect("Failed to add directory into database");
}

// FIXME: This is actually util command not database
Self::UpdateReleaseActivity => cratesfyi::utils::update_release_activity()
Self::UpdateReleaseActivity => cratesfyi::utils::update_release_activity(&conn)
.expect("Failed to update release activity"),

Self::DeleteCrate { crate_name } => {
let conn = db::connect_db().expect("failed to connect to the database");
db::delete_crate(&conn, &crate_name).expect("failed to delete the crate");
}

Self::Blacklist { command } => command.handle_args(),
Self::Blacklist { command } => command.handle_args(&conn),
}
}
}
Expand All @@ -467,9 +461,7 @@ enum BlacklistSubcommand {
}

impl BlacklistSubcommand {
fn handle_args(self) {
let conn = db::connect_db().expect("failed to connect to the database");

fn handle_args(self, conn: &Connection) {
match self {
Self::List => {
let crates =
Expand Down
34 changes: 28 additions & 6 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,59 @@
use failure::{bail, Error, Fail, ResultExt};
use failure::{bail, format_err, Error, Fail, ResultExt};
use std::env::VarError;
use std::str::FromStr;
use std::sync::Arc;

#[derive(Debug)]
pub struct Config {
// Database connection params
pub(crate) database_url: String,
pub(crate) max_pool_size: u32,
pub(crate) min_pool_idle: u32,

// Max size of the files served by the docs.rs frontend
pub(crate) max_file_size: usize,
pub(crate) max_file_size_html: usize,
}

impl Config {
pub fn from_env() -> Result<Self, Error> {
Ok(Self {
database_url: require_env("CRATESFYI_DATABASE_URL")?,
max_pool_size: env("DOCSRS_MAX_POOL_SIZE", 90)?,
min_pool_idle: env("DOCSRS_MIN_POOL_IDLE", 10)?,

max_file_size: env("DOCSRS_MAX_FILE_SIZE", 50 * 1024 * 1024)?,
max_file_size_html: env("DOCSRS_MAX_FILE_SIZE_HTML", 5 * 1024 * 1024)?,
})
}
}

impl iron::typemap::Key for Config {
type Value = Arc<Config>;
fn env<T>(var: &str, default: T) -> Result<T, Error>
where
T: FromStr,
T::Err: Fail,
{
Ok(maybe_env(var)?.unwrap_or(default))
}

fn require_env<T>(var: &str) -> Result<T, Error>
where
T: FromStr,
T::Err: Fail,
{
maybe_env(var)?.ok_or_else(|| format_err!("configuration variable {} is missing", var))
}

fn env<T>(var: &str, default: T) -> Result<T, Error>
fn maybe_env<T>(var: &str) -> Result<Option<T>, Error>
where
T: FromStr,
T::Err: Fail,
{
match std::env::var(var) {
Ok(content) => Ok(content
.parse::<T>()
.map(Some)
.with_context(|_| format!("failed to parse configuration variable {}", var))?),
Err(VarError::NotPresent) => Ok(default),
Err(VarError::NotPresent) => Ok(None),
Err(VarError::NotUnicode(_)) => bail!("configuration variable {} is not UTF-8", var),
}
}
55 changes: 2 additions & 53 deletions src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,11 @@ pub(crate) use self::add_package::add_package_into_database;
pub use self::delete_crate::delete_crate;
pub use self::file::add_path_into_database;
pub use self::migrate::migrate;

use failure::Fail;
use postgres::{Connection, TlsMode};
use std::env;
pub use self::pool::{Pool, PoolError};

mod add_package;
pub mod blacklist;
mod delete_crate;
pub(crate) mod file;
mod migrate;

/// Connects to database
pub fn connect_db() -> Result<Connection, failure::Error> {
let err = "CRATESFYI_DATABASE_URL environment variable is not set";
let db_url = env::var("CRATESFYI_DATABASE_URL").map_err(|e| e.context(err))?;
Connection::connect(&db_url[..], TlsMode::None).map_err(Into::into)
}

pub(crate) fn create_pool() -> r2d2::Pool<r2d2_postgres::PostgresConnectionManager> {
let db_url = env::var("CRATESFYI_DATABASE_URL")
.expect("CRATESFYI_DATABASE_URL environment variable is not exists");

let max_pool_size = env::var("DOCSRS_MAX_POOL_SIZE")
.map(|s| {
s.parse::<u32>()
.expect("DOCSRS_MAX_POOL_SIZE must be an integer")
})
.unwrap_or(90);
crate::web::metrics::MAX_DB_CONNECTIONS.set(max_pool_size as i64);

let min_pool_idle = env::var("DOCSRS_MIN_POOL_IDLE")
.map(|s| {
s.parse::<u32>()
.expect("DOCSRS_MIN_POOL_IDLE must be an integer")
})
.unwrap_or(10);

let manager =
r2d2_postgres::PostgresConnectionManager::new(&db_url[..], r2d2_postgres::TlsMode::None)
.expect("Failed to create PostgresConnectionManager");

r2d2::Pool::builder()
.max_size(max_pool_size)
.min_idle(Some(min_pool_idle))
.build(manager)
.expect("Failed to create r2d2 pool")
}

#[cfg(test)]
mod test {
use super::*;

#[test]
#[ignore]
fn test_connect_db() {
let conn = connect_db();
assert!(conn.is_ok());
}
}
mod pool;
Loading