Skip to content

Commit d2477f7

Browse files
authored
fix: cache bust jsr deps on constraint failure (denoland#22372)
Removes the `FileFetcher`'s internal cache because I don't believe it's necessary (we already cache this kind of stuff in places like deno_graph or config files in different places). Removing it fixes this bug because this functionality was already implemented in deno_graph and lowers memory usage of the CLI a little bit.
1 parent 34c8d17 commit d2477f7

File tree

9 files changed

+98
-50
lines changed

9 files changed

+98
-50
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ chrono = { version = "0.4", default-features = false, features = ["std", "serde"
9797
console_static_text = "=0.8.1"
9898
data-encoding = "2.3.3"
9999
data-url = "=0.3.0"
100+
deno_cache_dir = "=0.6.1"
100101
dlopen2 = "0.6.1"
101102
ecb = "=0.1.2"
102103
elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem"] }

cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ winres.workspace = true
6060

6161
[dependencies]
6262
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
63-
deno_cache_dir = "=0.6.1"
63+
deno_cache_dir = { workspace = true }
6464
deno_config = "=0.9.2"
6565
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
6666
deno_doc = { version = "=0.103.0", features = ["html"] }

cli/file_fetcher.rs

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,16 @@ impl File {
100100
}
101101
}
102102

103-
/// Simple struct implementing in-process caching to prevent multiple
104-
/// fs reads/net fetches for same file.
105103
#[derive(Debug, Clone, Default)]
106-
struct FileCache(Arc<Mutex<HashMap<ModuleSpecifier, File>>>);
104+
struct MemoryFiles(Arc<Mutex<HashMap<ModuleSpecifier, File>>>);
107105

108-
impl FileCache {
106+
impl MemoryFiles {
109107
pub fn get(&self, specifier: &ModuleSpecifier) -> Option<File> {
110-
let cache = self.0.lock();
111-
cache.get(specifier).cloned()
108+
self.0.lock().get(specifier).cloned()
112109
}
113110

114111
pub fn insert(&self, specifier: ModuleSpecifier, file: File) -> Option<File> {
115-
let mut cache = self.0.lock();
116-
cache.insert(specifier, file)
112+
self.0.lock().insert(specifier, file)
117113
}
118114
}
119115

@@ -157,7 +153,7 @@ pub struct FetchOptions<'a> {
157153
pub struct FileFetcher {
158154
auth_tokens: AuthTokens,
159155
allow_remote: bool,
160-
cache: FileCache,
156+
memory_files: MemoryFiles,
161157
cache_setting: CacheSetting,
162158
http_cache: Arc<dyn HttpCache>,
163159
http_client: Arc<HttpClient>,
@@ -178,7 +174,7 @@ impl FileFetcher {
178174
Self {
179175
auth_tokens: AuthTokens::new(env::var("DENO_AUTH_TOKENS").ok()),
180176
allow_remote,
181-
cache: Default::default(),
177+
memory_files: Default::default(),
182178
cache_setting,
183179
http_cache,
184180
http_client,
@@ -498,7 +494,7 @@ impl FileFetcher {
498494
debug!("FileFetcher::fetch() - specifier: {}", specifier);
499495
let scheme = get_validated_scheme(specifier)?;
500496
options.permissions.check_specifier(specifier)?;
501-
if let Some(file) = self.cache.get(specifier) {
497+
if let Some(file) = self.memory_files.get(specifier) {
502498
Ok(file)
503499
} else if scheme == "file" {
504500
// we do not in memory cache files, as this would prevent files on the
@@ -514,27 +510,23 @@ impl FileFetcher {
514510
format!("A remote specifier was requested: \"{specifier}\", but --no-remote is specified."),
515511
))
516512
} else {
517-
let result = self
513+
self
518514
.fetch_remote(
519515
specifier,
520516
options.permissions,
521517
10,
522518
options.maybe_accept.map(String::from),
523519
options.maybe_cache_setting.unwrap_or(&self.cache_setting),
524520
)
525-
.await;
526-
if let Ok(file) = &result {
527-
self.cache.insert(specifier.clone(), file.clone());
528-
}
529-
result
521+
.await
530522
}
531523
}
532524

533525
/// A synchronous way to retrieve a source file, where if the file has already
534526
/// been cached in memory it will be returned, otherwise for local files will
535527
/// be read from disk.
536528
pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option<File> {
537-
let maybe_file = self.cache.get(specifier);
529+
let maybe_file = self.memory_files.get(specifier);
538530
if maybe_file.is_none() {
539531
let is_local = specifier.scheme() == "file";
540532
if is_local {
@@ -548,9 +540,9 @@ impl FileFetcher {
548540
}
549541
}
550542

551-
/// Insert a temporary module into the in memory cache for the file fetcher.
552-
pub fn insert_cached(&self, file: File) -> Option<File> {
553-
self.cache.insert(file.specifier.clone(), file)
543+
/// Insert a temporary module for the file fetcher.
544+
pub fn insert_memory_files(&self, file: File) -> Option<File> {
545+
self.memory_files.insert(file.specifier.clone(), file)
554546
}
555547
}
556548

@@ -826,7 +818,7 @@ mod tests {
826818
"application/javascript".to_string(),
827819
)])),
828820
};
829-
file_fetcher.insert_cached(file.clone());
821+
file_fetcher.insert_memory_files(file.clone());
830822

831823
let result = file_fetcher
832824
.fetch(&specifier, PermissionsContainer::allow_all())
@@ -836,30 +828,6 @@ mod tests {
836828
assert_eq!(result_file, file);
837829
}
838830

839-
#[tokio::test]
840-
async fn test_get_source() {
841-
let _http_server_guard = test_util::http_server();
842-
let (file_fetcher, _) = setup(CacheSetting::Use, None);
843-
let specifier =
844-
resolve_url("http://localhost:4548/subdir/redirects/redirect1.js")
845-
.unwrap();
846-
847-
let result = file_fetcher
848-
.fetch(&specifier, PermissionsContainer::allow_all())
849-
.await;
850-
assert!(result.is_ok());
851-
852-
let maybe_file = file_fetcher.get_source(&specifier);
853-
assert!(maybe_file.is_some());
854-
let file = maybe_file.unwrap().into_text_decoded().unwrap();
855-
assert_eq!(file.source.as_ref(), "export const redirect = 1;\n");
856-
assert_eq!(
857-
file.specifier,
858-
resolve_url("http://localhost:4545/subdir/redirects/redirect1.js")
859-
.unwrap()
860-
);
861-
}
862-
863831
#[tokio::test]
864832
async fn test_fetch_data_url() {
865833
let (file_fetcher, _) = setup(CacheSetting::Use, None);

cli/tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ required-features = ["run"]
2424
bytes.workspace = true
2525
deno_ast.workspace = true
2626
deno_bench_util.workspace = true
27+
deno_cache_dir = { workspace = true }
2728
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting", "unsafe_use_unprotected_platform"] }
2829
deno_fetch.workspace = true
2930
deno_lockfile.workspace = true

cli/tests/integration/jsr_tests.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
22

3+
use deno_core::serde_json::Value;
34
use deno_lockfile::Lockfile;
45
use test_util as util;
6+
use url::Url;
57
use util::env_vars_for_jsr_tests;
68
use util::TestContextBuilder;
79

@@ -105,3 +107,77 @@ console.log(version);"#,
105107
.run()
106108
.assert_matches_text("0.1.0\n");
107109
}
110+
111+
#[test]
112+
fn reload_info_not_found_cache_but_exists_remote() {
113+
fn remove_version(registry_json: &mut Value, version: &str) {
114+
registry_json
115+
.as_object_mut()
116+
.unwrap()
117+
.get_mut("versions")
118+
.unwrap()
119+
.as_object_mut()
120+
.unwrap()
121+
.remove(version);
122+
}
123+
124+
fn remove_version_for_package(
125+
deno_dir: &util::TempDir,
126+
package: &str,
127+
version: &str,
128+
) {
129+
let specifier =
130+
Url::parse(&format!("http://127.0.0.1:4250/{}/meta.json", package))
131+
.unwrap();
132+
let registry_json_path = deno_dir
133+
.path()
134+
.join("deps")
135+
.join(deno_cache_dir::url_to_filename(&specifier).unwrap());
136+
let mut registry_json = registry_json_path.read_json_value();
137+
remove_version(&mut registry_json, version);
138+
registry_json_path.write_json(&registry_json);
139+
}
140+
141+
// This tests that when a local machine doesn't have a version
142+
// specified in a dependency that exists in the npm registry
143+
let test_context = TestContextBuilder::for_jsr().use_temp_cwd().build();
144+
let deno_dir = test_context.deno_dir();
145+
let temp_dir = test_context.temp_dir();
146+
temp_dir.write(
147+
"main.ts",
148+
"import { add } from 'jsr:@denotest/add@1'; console.log(add(1, 2));",
149+
);
150+
151+
// cache successfully to the deno_dir
152+
let output = test_context.new_command().args("cache main.ts").run();
153+
output.assert_matches_text(concat!(
154+
"Download http://127.0.0.1:4250/@denotest/add/meta.json\n",
155+
"Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json\n",
156+
"Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts\n",
157+
));
158+
159+
// modify the package information in the cache to remove the latest version
160+
remove_version_for_package(deno_dir, "@denotest/add", "1.0.0");
161+
162+
// should error when `--cache-only` is used now because the version is not in the cache
163+
let output = test_context
164+
.new_command()
165+
.args("run --cached-only main.ts")
166+
.run();
167+
output.assert_exit_code(1);
168+
output.assert_matches_text("error: Failed to resolve version constraint. Try running again without --cached-only
169+
at file:///[WILDCARD]main.ts:1:21
170+
");
171+
172+
// now try running without it, it should download the package now
173+
test_context
174+
.new_command()
175+
.args("run main.ts")
176+
.run()
177+
.assert_matches_text(concat!(
178+
"Download http://127.0.0.1:4250/@denotest/add/meta.json\n",
179+
"Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json\n",
180+
"3\n",
181+
))
182+
.assert_exit_code(0);
183+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Download http://127.0.0.1:4250/@denotest/deps/meta.json
2+
Download http://127.0.0.1:4250/@denotest/deps/meta.json
23
error: Could not find constraint in the list of versions: @denotest/[email protected]
34
Specifier: jsr:@denotest/[email protected]/mod.ts
45
at file:///[WILDCARD]/version_not_found/main.ts:1:19

cli/tools/run/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
9191
std::io::stdin().read_to_end(&mut source)?;
9292
// Save a fake file into file fetcher cache
9393
// to allow module access by TS compiler
94-
file_fetcher.insert_cached(File {
94+
file_fetcher.insert_memory_files(File {
9595
specifier: main_module.clone(),
9696
maybe_headers: None,
9797
source: source.into(),
@@ -174,7 +174,7 @@ pub async fn eval_command(
174174

175175
// Save a fake file into file fetcher cache
176176
// to allow module access by TS compiler.
177-
file_fetcher.insert_cached(File {
177+
file_fetcher.insert_memory_files(File {
178178
specifier: main_module.clone(),
179179
maybe_headers: None,
180180
source: source_code.into_bytes().into(),

cli/tools/test/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,7 @@ pub async fn check_specifiers(
873873
.collect();
874874

875875
for file in inline_files {
876-
file_fetcher.insert_cached(file);
876+
file_fetcher.insert_memory_files(file);
877877
}
878878

879879
module_load_preparer

0 commit comments

Comments
 (0)