diff --git a/crates/bin/docs_rs_web/src/handlers/build_details.rs b/crates/bin/docs_rs_web/src/handlers/build_details.rs index febd2c165..f8992619e 100644 --- a/crates/bin/docs_rs_web/src/handlers/build_details.rs +++ b/crates/bin/docs_rs_web/src/handlers/build_details.rs @@ -105,6 +105,19 @@ pub(crate) async fn build_details_handler( .await? .ok_or(AxumNope::BuildNotFound)?; + let metadata = MetaData::from_crate( + &mut conn, + params.name(), + &version, + Some(params.req_version().clone()), + ) + .await?; + let params = params.apply_metadata(&metadata); + + // NOTE: we want to give back the db connection to the pool + // before we do the long S3 requests. + drop(conn); + let (output, all_log_filenames, current_filename) = if let Some(output) = row.output { // legacy case, for old builds the build log was stored in the database. (output, Vec::new(), None) @@ -155,15 +168,6 @@ pub(crate) async fn build_details_handler( (file_content, all_log_filenames, current_filename) }; - let metadata = MetaData::from_crate( - &mut conn, - params.name(), - &version, - Some(params.req_version().clone()), - ) - .await?; - let params = params.apply_metadata(&metadata); - Ok(BuildDetailsPage { metadata, build_details: BuildDetails { diff --git a/crates/bin/docs_rs_web/src/handlers/crate_details.rs b/crates/bin/docs_rs_web/src/handlers/crate_details.rs index b652338db..6ecfad115 100644 --- a/crates/bin/docs_rs_web/src/handlers/crate_details.rs +++ b/crates/bin/docs_rs_web/src/handlers/crate_details.rs @@ -469,14 +469,18 @@ pub(crate) async fn crate_details_handler( let mut details = CrateDetails::from_matched_release(&mut conn, matched_release).await?; + let build_statistics = + BuildStatistics::fetch_for_release(&mut conn, details.crate_id, details.release_id).await?; + + // NOTE: we want to give back the db connection to the pool + // before we do the long S3 requests. + drop(conn); + match details.fetch_readme(&storage).await { Ok(readme) => details.readme = readme.or(details.readme), Err(e) => warn!(?e, "error fetching readme"), } - let build_statistics = - BuildStatistics::fetch_for_release(&mut conn, details.crate_id, details.release_id).await?; - let CrateDetails { version, name, diff --git a/crates/bin/docs_rs_web/src/handlers/rustdoc.rs b/crates/bin/docs_rs_web/src/handlers/rustdoc.rs index d1dcbc1cf..bb3b4f18e 100644 --- a/crates/bin/docs_rs_web/src/handlers/rustdoc.rs +++ b/crates/bin/docs_rs_web/src/handlers/rustdoc.rs @@ -34,6 +34,7 @@ use axum_extra::{ typed_header::TypedHeader, }; use docs_rs_cargo_metadata::Dependency; +use docs_rs_database::Pool; use docs_rs_headers::{ETagComputer, IfNoneMatch, X_ROBOTS_TAG}; use docs_rs_registry_api::OwnerKind; use docs_rs_rustdoc_json::RustdocJsonFormatVersion; @@ -186,13 +187,13 @@ pub(crate) struct RustdocRedirectorParams { /// Handler called for `/:crate` and `/:crate/:version` URLs. Automatically redirects to the docs /// or crate details page based on whether the given crate version was successfully built. #[allow(clippy::too_many_arguments)] -#[instrument(skip(storage, conn))] +#[instrument(skip(storage, pool))] pub(crate) async fn rustdoc_redirector_handler( Path(params): Path, original_uri: Uri, matched_path: MatchedPath, Extension(storage): Extension>, - mut conn: DbConnection, + Extension(pool): Extension, if_none_match: Option>, RawQuery(original_query): RawQuery, ) -> AxumResult { @@ -291,6 +292,9 @@ pub(crate) async fn rustdoc_redirector_handler( .map_err(AxumNope::BadRequest)? .with_page_kind(PageKind::Rustdoc); + // NOTE: we try to shorten the time we hold the database connection from the pool. + let mut conn = pool.get_async().await?; + // it doesn't matter if the version that was given was exact or not, since we're redirecting // anyway let matched_release = match_version(&mut conn, &crate_name, ¶ms.req_version().clone()) @@ -316,6 +320,10 @@ pub(crate) async fn rustdoc_redirector_handler( return async { let krate = CrateDetails::from_matched_release(&mut conn, matched_release).await?; + // NOTE: we want to give back the db connection to the pool + // before we do the long S3 requests. + drop(conn); + match storage .stream_rustdoc_file( params.name(), @@ -597,6 +605,10 @@ pub(crate) async fn rustdoc_html_server_handler( let krate = CrateDetails::from_matched_release(&mut conn, matched_release).await?; + // NOTE: we want to give back the db connection to the pool + // before we do the long S3 requests. + drop(conn); + trace!( ?params, doc_targets=?krate.metadata.doc_targets, @@ -784,9 +796,13 @@ pub(crate) async fn target_redirect_handler( .await? .into_canonical_req_version_or_else(|_, _| AxumNope::VersionNotFound)?; let params = params.apply_matched_release(&matched_release); + trace!(?params, "parsed params"); let crate_details = CrateDetails::from_matched_release(&mut conn, matched_release).await?; - trace!(?params, "parsed params"); + + // NOTE: we want to give back the db connection to the pool + // before we do the long S3 requests. + drop(conn); let storage_path = params.storage_path(); trace!(storage_path, "checking if path exists in other version"); @@ -893,6 +909,10 @@ pub(crate) async fn json_download_handler( let krate = CrateDetails::from_matched_release(&mut conn, matched_release).await?; + // NOTE: we want to give back the db connection to the pool + // before we do the long S3 requests. + drop(conn); + let wanted_format_version = if let Some(request_format_version) = json_params.format_version { // axum doesn't support extension suffixes in the route yet, not as parameter, and not // statically, when combined with a parameter (like `.../{format_version}.gz`). @@ -1001,6 +1021,11 @@ pub(crate) async fn download_handler( CachePolicy::ForeverInCdn(confirmed_name.into()), ) })?; + + // NOTE: we want to give back the db connection to the pool + // before we do the long S3 requests. + drop(conn); + params = params.apply_matched_release(&matched_release); let version = &matched_release.release.version; diff --git a/crates/bin/docs_rs_web/src/handlers/source.rs b/crates/bin/docs_rs_web/src/handlers/source.rs index a8beb67ff..be702cf76 100644 --- a/crates/bin/docs_rs_web/src/handlers/source.rs +++ b/crates/bin/docs_rs_web/src/handlers/source.rs @@ -246,6 +246,29 @@ pub(crate) async fn source_browser_handler( let inner_path = params.inner_path(); + let current_folder = if let Some(last_slash_pos) = inner_path.rfind('/') { + &inner_path[..last_slash_pos + 1] + } else { + "" + }; + let show_parent_link = !current_folder.is_empty(); + + let file_list = FileList::from_path(&mut conn, params.name(), version, current_folder) + .await? + .unwrap_or_default(); + + let metadata = MetaData::from_crate( + &mut conn, + params.name(), + version, + Some(params.req_version().clone()), + ) + .await?; + + // NOTE: we want to give back the db connection to the pool + // before we do the long S3 requests. + drop(conn); + // try to get actual file first // skip if request is a directory let stream = if !params.path_is_folder() { @@ -326,25 +349,6 @@ pub(crate) async fn source_browser_handler( (None, None) }; - let current_folder = if let Some(last_slash_pos) = inner_path.rfind('/') { - &inner_path[..last_slash_pos + 1] - } else { - "" - }; - let show_parent_link = !current_folder.is_empty(); - - let file_list = FileList::from_path(&mut conn, params.name(), version, current_folder) - .await? - .unwrap_or_default(); - - let metadata = MetaData::from_crate( - &mut conn, - params.name(), - version, - Some(params.req_version().clone()), - ) - .await?; - Ok(SourcePage { file_list, metadata, diff --git a/crates/lib/docs_rs_database/src/pool.rs b/crates/lib/docs_rs_database/src/pool.rs index b4bc926f8..9bc2e85c8 100644 --- a/crates/lib/docs_rs_database/src/pool.rs +++ b/crates/lib/docs_rs_database/src/pool.rs @@ -48,32 +48,38 @@ impl Pool { let max_lifetime = Duration::from_secs(30 * 60); let idle_timeout = Duration::from_secs(10 * 60); - let async_pool = PgPoolOptions::new() + let mut options = PgPoolOptions::new() .max_connections(config.max_pool_size) .min_connections(config.min_pool_idle) .max_lifetime(max_lifetime) .acquire_timeout(acquire_timeout) - .idle_timeout(idle_timeout) - .after_connect({ + .idle_timeout(idle_timeout); + + if cfg!(test) { + options = options.test_before_acquire(false); + } + + if schema != DEFAULT_SCHEMA { + options = options.after_connect({ let schema = schema.to_owned(); move |conn, _meta| { Box::pin({ let schema = schema.clone(); async move { - if schema != DEFAULT_SCHEMA { - conn.execute( - format!("SET search_path TO {schema}, {DEFAULT_SCHEMA};") - .as_str(), - ) - .await?; - } + conn.execute( + format!("SET search_path TO {schema}, {DEFAULT_SCHEMA};").as_str(), + ) + .await?; Ok(()) } }) } - }) + }); + } + + let async_pool = options .connect_lazy(&config.database_url) .map_err(PoolError::AsyncPoolCreationFailed)?;