1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-27 16:10:57 -05:00

cherry-pick deno#24983

This commit is contained in:
David Sherret 2024-08-10 16:57:02 -04:00 committed by losfair
parent b8f24ad5f1
commit 68f03a7ebf
10 changed files with 94 additions and 126 deletions

4
Cargo.lock generated
View file

@ -1281,9 +1281,7 @@ dependencies = [
[[package]] [[package]]
name = "deno_cache_dir" name = "deno_cache_dir"
version = "0.10.0" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4036ac8ce97244e2a66df7b97412592acaf14671900460d28415703ad790cd70"
dependencies = [ dependencies = [
"deno_media_type", "deno_media_type",
"indexmap", "indexmap",

View file

@ -377,3 +377,6 @@ opt-level = 3
opt-level = 3 opt-level = 3
[profile.release.package.zstd-sys] [profile.release.package.zstd-sys]
opt-level = 3 opt-level = 3
[patch.crates-io]
deno_cache_dir = { path = "../deno_cache_dir/rs_lib" }

8
cli/cache/mod.rs vendored
View file

@ -62,12 +62,8 @@ pub const CACHE_PERM: u32 = 0o644;
pub struct RealDenoCacheEnv; pub struct RealDenoCacheEnv;
impl deno_cache_dir::DenoCacheEnv for RealDenoCacheEnv { impl deno_cache_dir::DenoCacheEnv for RealDenoCacheEnv {
fn read_file_bytes(&self, path: &Path) -> std::io::Result<Option<Vec<u8>>> { fn read_file_bytes(&self, path: &Path) -> std::io::Result<Vec<u8>> {
match std::fs::read(path) { std::fs::read(path)
Ok(s) => Ok(Some(s)),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
} }
fn atomic_write_file( fn atomic_write_file(

View file

@ -271,8 +271,11 @@ impl CliFactory {
let global_cache = self.global_http_cache()?.clone(); let global_cache = self.global_http_cache()?.clone();
match self.options.vendor_dir_path() { match self.options.vendor_dir_path() {
Some(local_path) => { Some(local_path) => {
let local_cache = let local_cache = LocalHttpCache::new(
LocalHttpCache::new(local_path.clone(), global_cache); local_path.clone(),
global_cache,
deno_cache_dir::GlobalToLocalCopy::Allow,
);
Ok(Arc::new(local_cache)) Ok(Arc::new(local_cache))
} }
None => Ok(global_cache), None => Ok(global_cache),

View file

@ -11,7 +11,6 @@ use crate::http_util::HttpClientProvider;
use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBar;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context; use deno_core::anyhow::Context;
use deno_core::error::custom_error; use deno_core::error::custom_error;
use deno_core::error::generic_error; use deno_core::error::generic_error;
@ -52,6 +51,25 @@ pub enum FileOrRedirect {
Redirect(ModuleSpecifier), Redirect(ModuleSpecifier),
} }
impl FileOrRedirect {
fn from_deno_cache_entry(
specifier: &ModuleSpecifier,
cache_entry: deno_cache_dir::CacheEntry,
) -> Result<Self, AnyError> {
if let Some(redirect_to) = cache_entry.metadata.headers.get("location") {
let redirect =
deno_core::resolve_import(redirect_to, specifier.as_str())?;
Ok(FileOrRedirect::Redirect(redirect))
} else {
Ok(FileOrRedirect::File(File {
specifier: specifier.clone(),
maybe_headers: Some(cache_entry.metadata.headers),
source: Arc::from(cache_entry.content),
}))
}
}
}
/// A structure representing a source file. /// A structure representing a source file.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct File { pub struct File {
@ -238,45 +256,32 @@ impl FileFetcher {
); );
let cache_key = self.http_cache.cache_item_key(specifier)?; // compute this once let cache_key = self.http_cache.cache_item_key(specifier)?; // compute this once
let Some(headers) = self.http_cache.read_headers(&cache_key)? else { let result = self.http_cache.get(
return Ok(None);
};
if let Some(redirect_to) = headers.get("location") {
let redirect =
deno_core::resolve_import(redirect_to, specifier.as_str())?;
return Ok(Some(FileOrRedirect::Redirect(redirect)));
}
let result = self.http_cache.read_file_bytes(
&cache_key, &cache_key,
maybe_checksum maybe_checksum
.as_ref() .as_ref()
.map(|c| deno_cache_dir::Checksum::new(c.as_str())), .map(|c| deno_cache_dir::Checksum::new(c.as_str())),
deno_cache_dir::GlobalToLocalCopy::Allow,
); );
let bytes = match result { match result {
Ok(Some(bytes)) => bytes, Ok(Some(cache_data)) => Ok(Some(FileOrRedirect::from_deno_cache_entry(
Ok(None) => return Ok(None), specifier, cache_data,
)?)),
Ok(None) => Ok(None),
Err(err) => match err { Err(err) => match err {
deno_cache_dir::CacheReadFileError::Io(err) => return Err(err.into()), deno_cache_dir::CacheReadFileError::Io(err) => Err(err.into()),
deno_cache_dir::CacheReadFileError::ChecksumIntegrity(err) => { deno_cache_dir::CacheReadFileError::ChecksumIntegrity(err) => {
// convert to the equivalent deno_graph error so that it // convert to the equivalent deno_graph error so that it
// enhances it if this is passed to deno_graph // enhances it if this is passed to deno_graph
return Err( Err(
deno_graph::source::ChecksumIntegrityError { deno_graph::source::ChecksumIntegrityError {
actual: err.actual, actual: err.actual,
expected: err.expected, expected: err.expected,
} }
.into(), .into(),
); )
} }
}, },
}; }
Ok(Some(FileOrRedirect::File(File {
specifier: specifier.clone(),
maybe_headers: Some(headers),
source: Arc::from(bytes),
})))
} }
/// Convert a data URL into a file, resulting in an error if the URL is /// Convert a data URL into a file, resulting in an error if the URL is
@ -363,12 +368,30 @@ impl FileFetcher {
); );
} }
let maybe_etag = self let maybe_etag_cache_entry = self
.http_cache .http_cache
.cache_item_key(specifier) .cache_item_key(specifier)
.ok() .ok()
.and_then(|key| self.http_cache.read_headers(&key).ok().flatten()) .and_then(|key| {
.and_then(|headers| headers.get("etag").cloned()); self
.http_cache
.get(
&key,
maybe_checksum
.as_ref()
.map(|c| deno_cache_dir::Checksum::new(c.as_str())),
)
.ok()
.flatten()
})
.and_then(|cache_entry| {
cache_entry
.metadata
.headers
.get("etag")
.cloned()
.map(|etag| (cache_entry, etag))
});
let maybe_auth_token = self.auth_tokens.get(specifier); let maybe_auth_token = self.auth_tokens.get(specifier);
async fn handle_request_or_server_error( async fn handle_request_or_server_error(
@ -390,7 +413,6 @@ impl FileFetcher {
} }
} }
let mut maybe_etag = maybe_etag;
let mut retried = false; // retry intermittent failures let mut retried = false; // retry intermittent failures
let result = loop { let result = loop {
let result = match self let result = match self
@ -399,31 +421,17 @@ impl FileFetcher {
.fetch_no_follow(FetchOnceArgs { .fetch_no_follow(FetchOnceArgs {
url: specifier.clone(), url: specifier.clone(),
maybe_accept: maybe_accept.map(ToOwned::to_owned), maybe_accept: maybe_accept.map(ToOwned::to_owned),
maybe_etag: maybe_etag.clone(), maybe_etag: maybe_etag_cache_entry
.as_ref()
.map(|(_, etag)| etag.clone()),
maybe_auth_token: maybe_auth_token.clone(), maybe_auth_token: maybe_auth_token.clone(),
maybe_progress_guard: maybe_progress_guard.as_ref(), maybe_progress_guard: maybe_progress_guard.as_ref(),
}) })
.await? .await?
{ {
FetchOnceResult::NotModified => { FetchOnceResult::NotModified => {
let file_or_redirect = let (cache_entry, _) = maybe_etag_cache_entry.unwrap();
self.fetch_cached_no_follow(specifier, maybe_checksum)?; FileOrRedirect::from_deno_cache_entry(specifier, cache_entry)
match file_or_redirect {
Some(file_or_redirect) => Ok(file_or_redirect),
None => {
// Someone may have deleted the body from the cache since
// it's currently stored in a separate file from the headers,
// so delete the etag and try again
if maybe_etag.is_some() {
debug!("Cache body not found. Trying again without etag.");
maybe_etag = None;
continue;
} else {
// should never happen
bail!("Your deno cache directory is in an unrecoverable state. Please delete it and try again.")
}
}
}
} }
FetchOnceResult::Redirect(redirect_url, headers) => { FetchOnceResult::Redirect(redirect_url, headers) => {
self.http_cache.set(specifier, headers, &[])?; self.http_cache.set(specifier, headers, &[])?;
@ -1480,13 +1488,10 @@ mod tests {
let cache_key = file_fetcher.http_cache.cache_item_key(url).unwrap(); let cache_key = file_fetcher.http_cache.cache_item_key(url).unwrap();
let bytes = file_fetcher let bytes = file_fetcher
.http_cache .http_cache
.read_file_bytes( .get(&cache_key, None)
&cache_key,
None,
deno_cache_dir::GlobalToLocalCopy::Allow,
)
.unwrap() .unwrap()
.unwrap(); .unwrap()
.content;
String::from_utf8(bytes).unwrap() String::from_utf8(bytes).unwrap()
} }

View file

@ -17,16 +17,6 @@ use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
/// In the LSP, we disallow the cache from automatically copying from
/// the global cache to the local cache for technical reasons.
///
/// 1. We need to verify the checksums from the lockfile are correct when
/// moving from the global to the local cache.
/// 2. We need to verify the checksums for JSR https specifiers match what
/// is found in the package's manifest.
pub const LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY: deno_cache_dir::GlobalToLocalCopy =
deno_cache_dir::GlobalToLocalCopy::Disallow;
pub fn calculate_fs_version( pub fn calculate_fs_version(
cache: &LspCache, cache: &LspCache,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,

View file

@ -2,7 +2,6 @@
use super::cache::calculate_fs_version; use super::cache::calculate_fs_version;
use super::cache::LspCache; use super::cache::LspCache;
use super::cache::LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY;
use super::config::Config; use super::config::Config;
use super::resolver::LspResolver; use super::resolver::LspResolver;
use super::testing::TestCollector; use super::testing::TestCollector;
@ -872,22 +871,19 @@ impl FileSystemDocuments {
} else { } else {
let http_cache = cache.for_specifier(file_referrer); let http_cache = cache.for_specifier(file_referrer);
let cache_key = http_cache.cache_item_key(specifier).ok()?; let cache_key = http_cache.cache_item_key(specifier).ok()?;
let bytes = http_cache let cached_file = http_cache.get(&cache_key, None).ok()??;
.read_file_bytes(&cache_key, None, LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY)
.ok()??;
let specifier_headers = http_cache.read_headers(&cache_key).ok()??;
let (_, maybe_charset) = let (_, maybe_charset) =
deno_graph::source::resolve_media_type_and_charset_from_headers( deno_graph::source::resolve_media_type_and_charset_from_headers(
specifier, specifier,
Some(&specifier_headers), Some(&cached_file.metadata.headers),
); );
let content = deno_graph::source::decode_owned_source( let content = deno_graph::source::decode_owned_source(
specifier, specifier,
bytes, cached_file.content,
maybe_charset, maybe_charset,
) )
.ok()?; .ok()?;
let maybe_headers = Some(specifier_headers); let maybe_headers = Some(cached_file.metadata.headers);
Document::new( Document::new(
specifier.clone(), specifier.clone(),
content.into(), content.into(),

View file

@ -250,12 +250,9 @@ fn read_cached_url(
cache: &Arc<dyn HttpCache>, cache: &Arc<dyn HttpCache>,
) -> Option<Vec<u8>> { ) -> Option<Vec<u8>> {
cache cache
.read_file_bytes( .get(&cache.cache_item_key(url).ok()?, None)
&cache.cache_item_key(url).ok()?,
None,
deno_cache_dir::GlobalToLocalCopy::Disallow,
)
.ok()? .ok()?
.map(|f| f.content)
} }
// TODO(nayeemrmn): This is duplicated from a private function in deno_graph // TODO(nayeemrmn): This is duplicated from a private function in deno_graph

View file

@ -1,5 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_cache_dir::HttpCache;
use deno_core::serde_json;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_core::serde_json::Value; use deno_core::serde_json::Value;
use deno_lockfile::Lockfile; use deno_lockfile::Lockfile;
@ -183,13 +185,24 @@ fn reload_info_not_found_cache_but_exists_remote() {
let specifier = let specifier =
Url::parse(&format!("http://127.0.0.1:4250/{}/meta.json", package)) Url::parse(&format!("http://127.0.0.1:4250/{}/meta.json", package))
.unwrap(); .unwrap();
let registry_json_path = deno_dir let cache = deno_cache_dir::GlobalHttpCache::new(
.path() deno_dir.path().join("deps").to_path_buf(),
.join("deps") deno_cache_dir::TestRealDenoCacheEnv,
.join(deno_cache_dir::url_to_filename(&specifier).unwrap()); );
let mut registry_json = registry_json_path.read_json_value(); let entry = cache
.get(&cache.cache_item_key(&specifier).unwrap(), None)
.unwrap()
.unwrap();
let mut registry_json: serde_json::Value =
serde_json::from_slice(&entry.content).unwrap();
remove_version(&mut registry_json, version); remove_version(&mut registry_json, version);
registry_json_path.write_json(&registry_json); cache
.set(
&specifier,
entry.metadata.headers.clone(),
&registry_json.to_string().as_bytes(),
)
.unwrap();
} }
// This tests that when a local machine doesn't have a version // This tests that when a local machine doesn't have a version

View file

@ -5042,39 +5042,6 @@ console.log(add(3, 4));
output.assert_matches_text("[WILDCARD]5\n7\n"); output.assert_matches_text("[WILDCARD]5\n7\n");
} }
#[test]
fn run_etag_delete_source_cache() {
let test_context = TestContextBuilder::new()
.use_temp_cwd()
.use_http_server()
.build();
test_context
.temp_dir()
.write("main.ts", "import 'http://localhost:4545/etag_script.ts'");
test_context
.new_command()
.args("cache main.ts")
.run()
.skip_output_check();
// The cache is currently stored unideally in two files where one file has the headers
// and the other contains the body. An issue can happen with the etag header where the
// headers file exists, but the body was deleted. We need to get the cache to gracefully
// handle this scenario.
let deno_dir = test_context.deno_dir().path();
let etag_script_path = deno_dir.join("deps/http/localhost_PORT4545/26110db7d42c9bad32386735cbc05c301f83e4393963deb8da14fec3b4202a13");
assert!(etag_script_path.exists());
etag_script_path.remove_file();
test_context
.new_command()
.args("cache --reload --log-level=debug main.ts")
.run()
.assert_matches_text(
"[WILDCARD]Cache body not found. Trying again without etag.[WILDCARD]",
);
}
#[test] #[test]
fn code_cache_test() { fn code_cache_test() {
let test_context = TestContextBuilder::new().use_temp_cwd().build(); let test_context = TestContextBuilder::new().use_temp_cwd().build();