Skip to content

Commit

Permalink
fix: delete Deno 1.x cache entry on get (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret authored Aug 28, 2024
1 parent c60583a commit ed098c6
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 90 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.vscode
/target
lib/snippets
lib/deno_cache_dir.generated.js
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"Cargo.lock",
"rs_lib",
"**/*.toml",
"!lib/snippets/",
"!lib/deno_cache_dir.generated.js"
]
},
Expand Down
86 changes: 0 additions & 86 deletions lib/snippets/deno_cache_dir-2cdd9fa3c5b50ad3/fs.js

This file was deleted.

4 changes: 4 additions & 0 deletions rs_lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export function read_file_bytes(path) {
}
}

export function remove_file(path) {
Deno.removeSync(path);
}

export function atomic_write_file(path, bytes) {
function parentPath(path) {
const lastSlashIndex = path.lastIndexOf("/") ?? path.lastIndexOf("\\");
Expand Down
5 changes: 5 additions & 0 deletions rs_lib/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub trait DenoCacheEnv: Send + Sync + std::fmt::Debug + Clone {
fn read_file_bytes(&self, path: &Path) -> std::io::Result<Vec<u8>>;
fn atomic_write_file(&self, path: &Path, bytes: &[u8])
-> std::io::Result<()>;
fn remove_file(&self, path: &Path) -> std::io::Result<()>;
fn modified(&self, path: &Path) -> std::io::Result<Option<SystemTime>>;
fn is_file(&self, path: &Path) -> bool;
fn time_now(&self) -> SystemTime;
Expand Down Expand Up @@ -48,6 +49,10 @@ mod test_fs {
}
}

fn remove_file(&self, path: &Path) -> std::io::Result<()> {
std::fs::remove_file(path)
}

fn modified(&self, path: &Path) -> std::io::Result<Option<SystemTime>> {
match std::fs::metadata(path) {
Ok(metadata) => Ok(Some(
Expand Down
11 changes: 8 additions & 3 deletions rs_lib/src/global/cache_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,25 @@ pub fn write(
Ok(())
}

pub enum ReadError {
Io(std::io::Error),
InvalidFormat,
}

pub fn read(
env: &impl DenoCacheEnv,
path: &Path,
) -> std::io::Result<Option<CacheEntry>> {
) -> Result<Option<CacheEntry>, ReadError> {
let mut original_file_bytes = match env.read_file_bytes(path) {
Ok(file) => file,
Err(err) if err.kind() == ErrorKind::NotFound => return Ok(None),
Err(err) => return Err(err),
Err(err) => return Err(ReadError::Io(err)),
};

let Some((content, metadata)) =
read_content_and_metadata(&original_file_bytes)
else {
return Ok(None);
return Err(ReadError::InvalidFormat);
};

// truncate the original bytes to just the content
Expand Down
42 changes: 41 additions & 1 deletion rs_lib/src/global/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use std::path::Path;
use std::path::PathBuf;
use std::time::Duration;
use std::time::SystemTime;
Expand Down Expand Up @@ -126,7 +127,17 @@ impl<Env: DenoCacheEnv> HttpCache for GlobalHttpCache<Env> {
#[cfg(debug_assertions)]
debug_assert!(!key.is_local_key);

let maybe_file = cache_file::read(&self.env, self.key_file_path(key))?;
let file_path = self.key_file_path(key);
let maybe_file = match cache_file::read(&self.env, file_path) {
Ok(maybe_file) => maybe_file,
Err(cache_file::ReadError::Io(err)) => {
return Err(CacheReadFileError::Io(err))
}
Err(cache_file::ReadError::InvalidFormat) => {
handle_maybe_deno_1_x_cache_entry(&self.env, file_path);
None
}
};

if let Some(file) = &maybe_file {
if let Some(expected_checksum) = maybe_checksum {
Expand Down Expand Up @@ -188,6 +199,35 @@ impl<Env: DenoCacheEnv> HttpCache for GlobalHttpCache<Env> {
}
}

fn handle_maybe_deno_1_x_cache_entry(
env: &impl DenoCacheEnv,
file_path: &Path,
) {
// Deno 1.x structures its cache in two separate files using the same name for
// the content, but a separate <filename>.metadata.json file for the headers.
//
// We don't want the following scenario to happen:
//
// 1. User generates the cache on Deno 1.x.
// - <filename> and <filename>.metadata.json are created.
// 2. User updates to Deno 2 and is updated to the new cache.
// - <filename> is updated to new single file format
// 3. User downgrades to Deno 1.x.
// - <filename> is now using the new Deno 2.0 format which
// is incorrect and has a different content than if they
// cached on Deno 1.x
//
// To prevent this scenario, check for the precence of the Deno 1.x
// <filename>.metadata.json file. If it exists, delete it.
let metadata_file = file_path.with_extension("metadata.json");
if env.is_file(&metadata_file) {
// delete the Deno 1.x cache files, deleting the metadata.json
// file first in case the process exits between these two statements
let _ = env.remove_file(&file_path.with_extension("metadata.json"));
let _ = env.remove_file(file_path);
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
6 changes: 6 additions & 0 deletions rs_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pub mod wasm {
#[wasm_bindgen(catch)]
fn read_file_bytes(path: &str) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
fn remove_file(path: &str) -> Result<(), JsValue>;
#[wasm_bindgen(catch)]
fn atomic_write_file(path: &str, bytes: &[u8]) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
fn modified_time(path: &str) -> Result<Option<usize>, JsValue>;
Expand Down Expand Up @@ -82,6 +84,10 @@ pub mod wasm {
Ok(())
}

fn remove_file(&self, path: &Path) -> std::io::Result<()> {
remove_file(&path.to_string_lossy()).map_err(js_to_io_error)
}

fn modified(&self, path: &Path) -> std::io::Result<Option<SystemTime>> {
if let Some(time) =
modified_time(&path.to_string_lossy()).map_err(js_to_io_error)?
Expand Down
26 changes: 26 additions & 0 deletions rs_lib/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Arc;

use deno_cache_dir::CacheReadFileError;
use deno_cache_dir::Checksum;
use deno_cache_dir::DenoCacheEnv;
use deno_cache_dir::GlobalHttpCache;
use deno_cache_dir::GlobalToLocalCopy;
use deno_cache_dir::HttpCache;
Expand Down Expand Up @@ -91,6 +92,31 @@ fn test_global_get_set() {
}
}

#[test]
fn test_global_deno_1_x_metadata_existing() {
let dir = TempDir::new().unwrap();
let fs = TestRealDenoCacheEnv;
let cache_dir_path = dir.path().to_path_buf();
let cache = GlobalHttpCache::new(cache_dir_path.clone(), fs.clone());
// add the two files for deno 1.x
let deno_land_dir = cache_dir_path.join("https").join("deno.land");
let cached_file_path = deno_land_dir
.join("c565f9618e105d73fa1b5ffcbdae2b2e934d087839c2807aa83b4e60149adaf8");
fs.atomic_write_file(&cached_file_path, &[]).unwrap();
let metadata_file_path = cached_file_path.with_extension("metadata.json");
fs.atomic_write_file(&metadata_file_path, &[]).unwrap();
assert!(cached_file_path.exists());
assert!(metadata_file_path.exists());

let url = Url::parse("https://deno.land/x/welcome.ts").unwrap();
let key = cache.cache_item_key(&url).unwrap();
// checking if the cache entry exists will delete the Deno 1.x cache entry
let entry = cache.get(&key, None).unwrap();
assert!(!metadata_file_path.exists());
assert!(!cached_file_path.exists());
assert!(entry.is_none());
}

#[test]
fn test_local_global_cache() {
let temp_dir = TempDir::new().unwrap();
Expand Down

0 comments on commit ed098c6

Please sign in to comment.