Skip to content

Commit 7bd57a4

Browse files
committed
add optional request timeout & reporting for it
1 parent d9119d0 commit 7bd57a4

File tree

3 files changed

+38
-3
lines changed

3 files changed

+38
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ axum-extra = "0.5.0"
9191
hyper = { version = "0.14.15", default-features = false }
9292
tower = "0.4.11"
9393
tower-service = "0.3.2"
94-
tower-http = { version = "0.4.0", features = ["fs", "trace"] }
94+
tower-http = { version = "0.4.0", features = ["fs", "trace", "timeout"] }
9595
mime = "0.3.16"
9696
percent-encoding = "2.2.0"
9797

src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ pub struct Config {
4141
// Gitlab authentication
4242
pub(crate) gitlab_accesstoken: Option<String>,
4343

44+
// request timeout in seconds
45+
pub(crate) request_timeout: Option<u64>,
46+
pub(crate) report_request_timeouts: bool,
47+
4448
// Max size of the files served by the docs.rs frontend
4549
pub(crate) max_file_size: usize,
4650
pub(crate) max_file_size_html: usize,
@@ -157,6 +161,8 @@ impl Config {
157161
max_parse_memory: env("DOCSRS_MAX_PARSE_MEMORY", 5 * 1024 * 1024)?,
158162
registry_gc_interval: env("DOCSRS_REGISTRY_GC_INTERVAL", 60 * 60)?,
159163
render_threads: env("DOCSRS_RENDER_THREADS", num_cpus::get())?,
164+
request_timeout: maybe_env("DOCSRS_REQUEST_TIMEOUT")?,
165+
report_request_timeouts: env("DOCSRS_REPORT_REQUEST_TIMEOUTS", false)?,
160166

161167
random_crate_search_view_size: env("DOCSRS_RANDOM_CRATE_SEARCH_VIEW_SIZE", 500)?,
162168

src/web/mod.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod page;
55
use crate::utils::get_correct_docsrs_style_file;
66
use crate::utils::spawn_blocking;
77
use anyhow::{anyhow, bail, Context as _, Result};
8+
use axum_extra::middleware::option_layer;
89
use serde_json::Value;
910
use tracing::{info, instrument};
1011

@@ -30,7 +31,13 @@ mod statics;
3031
use crate::{db::Pool, impl_axum_webpage, Context};
3132
use anyhow::Error;
3233
use axum::{
33-
extract::Extension, http::StatusCode, middleware, response::IntoResponse, Router as AxumRouter,
34+
extract::Extension,
35+
http::Request as AxumRequest,
36+
http::StatusCode,
37+
middleware,
38+
middleware::Next,
39+
response::{IntoResponse, Response as AxumResponse},
40+
Router as AxumRouter,
3441
};
3542
use chrono::{DateTime, Utc};
3643
use error::AxumNope;
@@ -40,9 +47,10 @@ use postgres::Client;
4047
use semver::{Version, VersionReq};
4148
use serde::Serialize;
4249
use std::borrow::Borrow;
50+
use std::time::Duration;
4351
use std::{borrow::Cow, net::SocketAddr, sync::Arc};
4452
use tower::ServiceBuilder;
45-
use tower_http::trace::TraceLayer;
53+
use tower_http::{timeout::TimeoutLayer, trace::TraceLayer};
4654
use url::form_urlencoded;
4755

4856
// from https://github.com/servo/rust-url/blob/master/url/src/parser.rs
@@ -243,18 +251,39 @@ async fn match_version_axum(
243251
.await
244252
}
245253

254+
async fn log_timeouts_to_sentry<B>(req: AxumRequest<B>, next: Next<B>) -> AxumResponse {
255+
let uri = req.uri().clone();
256+
257+
let response = next.run(req).await;
258+
259+
if response.status() == StatusCode::REQUEST_TIMEOUT {
260+
tracing::error!(?uri, "request timeout");
261+
}
262+
263+
response
264+
}
265+
246266
#[instrument(skip_all)]
247267
pub(crate) fn build_axum_app(
248268
context: &dyn Context,
249269
template_data: Arc<TemplateData>,
250270
) -> Result<AxumRouter, Error> {
271+
let config = context.config()?;
251272
Ok(routes::build_axum_routes().layer(
252273
// It’s recommended to use tower::ServiceBuilder to apply multiple middleware at once,
253274
// instead of calling Router::layer repeatedly:
254275
ServiceBuilder::new()
255276
.layer(TraceLayer::new_for_http())
256277
.layer(sentry_tower::NewSentryLayer::new_from_top())
257278
.layer(sentry_tower::SentryHttpLayer::with_transaction())
279+
.layer(option_layer(
280+
config
281+
.report_request_timeouts
282+
.then_some(middleware::from_fn(log_timeouts_to_sentry)),
283+
))
284+
.layer(option_layer(config.request_timeout.map(|timeout| {
285+
TimeoutLayer::new(Duration::from_secs(timeout))
286+
})))
258287
.layer(Extension(context.pool()?))
259288
.layer(Extension(context.build_queue()?))
260289
.layer(Extension(context.metrics()?))

0 commit comments

Comments
 (0)