mirror of
https://github.com/denoland/deno.git
synced 2024-12-23 07:44:48 -05:00
fix(ext/cache): cache.put overwrites previous call (#18649)
This commit is contained in:
parent
65b0d2316d
commit
18e0ee368c
3 changed files with 85 additions and 22 deletions
|
@ -127,3 +127,57 @@ fn cache_matching_package_json_dep_should_not_install_all() {
|
||||||
"Initialize @types/node@18.8.2\n",
|
"Initialize @types/node@18.8.2\n",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test for https://github.com/denoland/deno/issues/17299
|
||||||
|
#[test]
|
||||||
|
fn cache_put_overwrite() {
|
||||||
|
let test_context = TestContextBuilder::new().use_temp_cwd().build();
|
||||||
|
let temp_dir = test_context.temp_dir();
|
||||||
|
|
||||||
|
let part_one = r#"
|
||||||
|
const req = new Request('http://localhost/abc');
|
||||||
|
const res1 = new Response('res1');
|
||||||
|
const res2 = new Response('res2');
|
||||||
|
|
||||||
|
const cache = await caches.open('test');
|
||||||
|
|
||||||
|
await cache.put(req, res1);
|
||||||
|
await cache.put(req, res2);
|
||||||
|
|
||||||
|
const res = await cache.match(req).then((res) => res?.text());
|
||||||
|
console.log(res);
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let part_two = r#"
|
||||||
|
const req = new Request("http://localhost/abc");
|
||||||
|
const res1 = new Response("res1");
|
||||||
|
const res2 = new Response("res2");
|
||||||
|
|
||||||
|
const cache = await caches.open("test");
|
||||||
|
|
||||||
|
// Swap the order of put() calls.
|
||||||
|
await cache.put(req, res2);
|
||||||
|
await cache.put(req, res1);
|
||||||
|
|
||||||
|
const res = await cache.match(req).then((res) => res?.text());
|
||||||
|
console.log(res);
|
||||||
|
"#;
|
||||||
|
|
||||||
|
temp_dir.write("cache_put.js", part_one);
|
||||||
|
|
||||||
|
let run_command =
|
||||||
|
test_context.new_command().args_vec(["run", "cache_put.js"]);
|
||||||
|
|
||||||
|
let output = run_command.run();
|
||||||
|
output.assert_matches_text("res2\n");
|
||||||
|
output.assert_exit_code(0);
|
||||||
|
|
||||||
|
// The wait will surface the bug as we check last written time
|
||||||
|
// when we overwrite a response.
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
|
|
||||||
|
temp_dir.write("cache_put.js", part_two);
|
||||||
|
let output = run_command.run();
|
||||||
|
output.assert_matches_text("res1\n");
|
||||||
|
output.assert_exit_code(0);
|
||||||
|
}
|
||||||
|
|
|
@ -173,3 +173,20 @@ Deno.test(async function cachePutFailedBody() {
|
||||||
// if it fails to read the body, the cache should be empty
|
// if it fails to read the body, the cache should be empty
|
||||||
assertEquals(response, undefined);
|
assertEquals(response, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test(async function cachePutOverwrite() {
|
||||||
|
const cacheName = "cache-v1";
|
||||||
|
const cache = await caches.open(cacheName);
|
||||||
|
|
||||||
|
const request = new Request("https://example.com/overwrite");
|
||||||
|
const res1 = new Response("res1");
|
||||||
|
const res2 = new Response("res2");
|
||||||
|
|
||||||
|
await cache.put(request, res1);
|
||||||
|
const res = await cache.match(request);
|
||||||
|
assertEquals(await res?.text(), "res1");
|
||||||
|
|
||||||
|
await cache.put(request, res2);
|
||||||
|
const res_ = await cache.match(request);
|
||||||
|
assertEquals(await res_?.text(), "res2");
|
||||||
|
});
|
||||||
|
|
34
ext/cache/sqlite.rs
vendored
34
ext/cache/sqlite.rs
vendored
|
@ -285,29 +285,11 @@ impl Cache for SqliteBackedCache {
|
||||||
async fn insert_cache_asset(
|
async fn insert_cache_asset(
|
||||||
db: Arc<Mutex<rusqlite::Connection>>,
|
db: Arc<Mutex<rusqlite::Connection>>,
|
||||||
put: CachePutRequest,
|
put: CachePutRequest,
|
||||||
body_key_start_time: Option<(String, u64)>,
|
response_body_key: Option<String>,
|
||||||
) -> Result<Option<String>, deno_core::anyhow::Error> {
|
) -> Result<Option<String>, deno_core::anyhow::Error> {
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let maybe_response_body = {
|
let maybe_response_body = {
|
||||||
let db = db.lock();
|
let db = db.lock();
|
||||||
let mut response_body_key = None;
|
|
||||||
if let Some((body_key, start_time)) = body_key_start_time {
|
|
||||||
response_body_key = Some(body_key);
|
|
||||||
let last_inserted_at = db.query_row("
|
|
||||||
SELECT last_inserted_at FROM request_response_list
|
|
||||||
WHERE cache_id = ?1 AND request_url = ?2",
|
|
||||||
(put.cache_id, &put.request_url), |row| {
|
|
||||||
let last_inserted_at: i64 = row.get(0)?;
|
|
||||||
Ok(last_inserted_at)
|
|
||||||
}).optional()?;
|
|
||||||
if let Some(last_inserted) = last_inserted_at {
|
|
||||||
// Some other worker has already inserted this resource into the cache.
|
|
||||||
// Note: okay to unwrap() as it is always present when response_body_key is present.
|
|
||||||
if start_time > (last_inserted as u64) {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db.query_row(
|
db.query_row(
|
||||||
"INSERT OR REPLACE INTO request_response_list
|
"INSERT OR REPLACE INTO request_response_list
|
||||||
(cache_id, request_url, request_headers, response_headers,
|
(cache_id, request_url, request_headers, response_headers,
|
||||||
|
@ -368,14 +350,24 @@ impl CachePutResource {
|
||||||
let mut file = resource.borrow_mut().await;
|
let mut file = resource.borrow_mut().await;
|
||||||
file.flush().await?;
|
file.flush().await?;
|
||||||
file.sync_all().await?;
|
file.sync_all().await?;
|
||||||
insert_cache_asset(
|
let maybe_body_key = insert_cache_asset(
|
||||||
self.db.clone(),
|
self.db.clone(),
|
||||||
self.put_request.clone(),
|
self.put_request.clone(),
|
||||||
Some((self.response_body_key.clone(), self.start_time)),
|
Some(self.response_body_key.clone()),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
match maybe_body_key {
|
||||||
|
Some(key) => {
|
||||||
|
assert_eq!(key, self.response_body_key);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
// This should never happen because we will always have
|
||||||
|
// body key associated with CachePutResource
|
||||||
|
None => Err(deno_core::anyhow::anyhow!(
|
||||||
|
"unexpected: response body key is None"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resource for CachePutResource {
|
impl Resource for CachePutResource {
|
||||||
|
|
Loading…
Reference in a new issue