Skip to content

Commit 5772284

Browse files
committed
feat: Revalidate HTTP cache entries once per minute maximum
This is to avoid revalidating HTTP cache too frequently (and have many parallel revalidation tasks) if revalidation fails or the HTTP request takes some time. The stale period >= 1 hour, so 1 more minute won't be a problem.
1 parent beb6a21 commit 5772284

File tree

1 file changed

+26
-10
lines changed

1 file changed

+26
-10
lines changed

src/net/http.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ async fn http_cache_put(context: &Context, url: &str, response: &Response) -> Re
151151
/// Also returns if the response is stale and should be revalidated in the background.
152152
async fn http_cache_get(context: &Context, url: &str) -> Result<Option<(Response, bool)>> {
153153
let now = time();
154-
let Some((blob_name, mimetype, encoding, is_stale)) = context
154+
let Some((blob_name, mimetype, encoding, stale_timestamp)) = context
155155
.sql
156156
.query_row_optional(
157157
"SELECT blobname, mimetype, encoding, stale
@@ -162,13 +162,14 @@ async fn http_cache_get(context: &Context, url: &str) -> Result<Option<(Response
162162
let mimetype: Option<String> = Some(row.get(1)?).filter(|s: &String| !s.is_empty());
163163
let encoding: Option<String> = Some(row.get(2)?).filter(|s: &String| !s.is_empty());
164164
let stale_timestamp: i64 = row.get(3)?;
165-
Ok((blob_name, mimetype, encoding, now > stale_timestamp))
165+
Ok((blob_name, mimetype, encoding, stale_timestamp))
166166
},
167167
)
168168
.await?
169169
else {
170170
return Ok(None);
171171
};
172+
let is_stale = now > stale_timestamp;
172173

173174
let blob_object = BlobObject::from_name(context, blob_name)?;
174175
let blob_abs_path = blob_object.to_abs_path();
@@ -195,15 +196,16 @@ async fn http_cache_get(context: &Context, url: &str) -> Result<Option<(Response
195196
// Update expiration timestamp
196197
// to prevent deletion of the file still in use.
197198
//
198-
// We do not update stale timestamp here
199-
// as we have not revalidated the response.
200-
// Stale timestamp is updated only
201-
// when the URL is sucessfully fetched.
199+
// If the response is stale, the caller should revalidate it in the background, so update
200+
// `stale` timestamp to avoid revalidating too frequently (and have many parallel revalidation
201+
// tasks) if revalidation fails or the HTTP request takes some time. The stale period >= 1 hour,
202+
// so 1 more minute won't be a problem.
203+
let stale_timestamp = if is_stale { now + 60 } else { stale_timestamp };
202204
context
203205
.sql
204206
.execute(
205-
"UPDATE http_cache SET expires=? WHERE url=?",
206-
(expires, url),
207+
"UPDATE http_cache SET expires=?, stale=? WHERE url=?",
208+
(expires, stale_timestamp, url),
207209
)
208210
.await?;
209211

@@ -305,8 +307,6 @@ pub async fn read_url_blob(context: &Context, url: &str) -> Result<Response> {
305307
}
306308
});
307309
}
308-
309-
// Return stale result.
310310
return Ok(response);
311311
}
312312

@@ -495,6 +495,22 @@ mod tests {
495495
);
496496
assert_eq!(http_cache_get(t, xdc_pixel_url).await?, None);
497497

498+
// If we get the blob the second time quickly, it shouldn't be stale because it's supposed
499+
// that we've already run a revalidation task which will update the blob soon.
500+
assert_eq!(
501+
http_cache_get(t, xdc_editor_url).await?,
502+
Some((xdc_response.clone(), false))
503+
);
504+
// But if the revalidation task hasn't succeeded after some time, the blob is stale again
505+
// even if we continue to get it frequently.
506+
for i in (0..100).rev() {
507+
SystemTime::shift(Duration::from_secs(6));
508+
if let Some((_, true)) = http_cache_get(t, xdc_editor_url).await? {
509+
break;
510+
}
511+
assert!(i > 0);
512+
}
513+
498514
// Test that if the file is accidentally removed from the blobdir,
499515
// there is no error when trying to load the cache entry.
500516
for entry in std::fs::read_dir(t.get_blobdir())? {

0 commit comments

Comments
 (0)