Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(emit): eliminate multiple cache files getting out of sync and fix racy emit #13462

Closed
wants to merge 12 commits into from
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pin-project = "=1.0.8"
rand = { version = "=0.8.4", features = ["small_rng"] }
regex = "=1.5.4"
ring = "=0.16.20"
rusqlite = { version = "0.25.3", features = ["unlock_notify", "bundled"] }
rustyline = { version = "=9.0.0", default-features = false }
rustyline-derive = "=0.5.0"
semver-parser = "=0.10.2"
Expand Down
232 changes: 5 additions & 227 deletions cli/cache.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.

use crate::disk_cache::DiskCache;
use crate::errors::get_error_class_name;
use crate::file_fetcher::FileFetcher;

use deno_core::error::AnyError;
use deno_core::futures::future;
use deno_core::futures::FutureExt;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::serde_json;
use deno_core::ModuleSpecifier;
use deno_graph::source::CacheInfo;
use deno_graph::source::LoadFuture;
Expand All @@ -19,116 +14,38 @@ use deno_runtime::permissions::Permissions;
use std::collections::HashMap;
use std::sync::Arc;

#[derive(Debug, Deserialize, Serialize)]
pub struct EmitMetadata {
pub version_hash: String,
}

pub(crate) enum CacheType {
Declaration,
Emit,
SourceMap,
TypeScriptBuildInfo,
Version,
}

/// A trait which provides a concise implementation to getting and setting
/// values in a cache.
pub(crate) trait Cacher {
/// Get a value from the cache.
fn get(
&self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
) -> Option<String>;
/// Set a value in the cache.
fn set(
&mut self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
value: String,
) -> Result<(), AnyError>;
}

/// Combines the cacher trait along with the deno_graph Loader trait to provide
/// a single interface to be able to load and cache modules when building a
/// graph.
pub(crate) trait CacherLoader: Cacher + Loader {
fn as_cacher(&self) -> &dyn Cacher;
fn as_mut_loader(&mut self) -> &mut dyn Loader;
fn as_mut_cacher(&mut self) -> &mut dyn Cacher;
}

/// A "wrapper" for the FileFetcher and DiskCache for the Deno CLI that provides
/// A "wrapper" for the FileFetcher for the Deno CLI that provides
/// a concise interface to the DENO_DIR when building module graphs.
pub(crate) struct FetchCacher {
disk_cache: DiskCache,
dynamic_permissions: Permissions,
file_fetcher: Arc<FileFetcher>,
root_permissions: Permissions,
}

impl FetchCacher {
pub fn new(
disk_cache: DiskCache,
file_fetcher: FileFetcher,
root_permissions: Permissions,
dynamic_permissions: Permissions,
) -> Self {
let file_fetcher = Arc::new(file_fetcher);

Self {
disk_cache,
dynamic_permissions,
file_fetcher,
root_permissions,
}
}

fn get_emit_metadata(
&self,
specifier: &ModuleSpecifier,
) -> Option<EmitMetadata> {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, "meta")?;
let bytes = self.disk_cache.get(&filename).ok()?;
serde_json::from_slice(&bytes).ok()
}

fn set_emit_metadata(
&self,
specifier: &ModuleSpecifier,
data: EmitMetadata,
) -> Result<(), AnyError> {
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, "meta")
.unwrap();
let bytes = serde_json::to_vec(&data)?;
self.disk_cache.set(&filename, &bytes).map_err(|e| e.into())
}
}

impl Loader for FetchCacher {
fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option<CacheInfo> {
let local = self.file_fetcher.get_local_path(specifier)?;
if local.is_file() {
let location = &self.disk_cache.location;
let emit = self
.disk_cache
.get_cache_filename_with_extension(specifier, "js")
.map(|p| location.join(p))
.filter(|p| p.is_file());
let map = self
.disk_cache
.get_cache_filename_with_extension(specifier, "js.map")
.map(|p| location.join(p))
.filter(|p| p.is_file());
Some(CacheInfo {
local: Some(local),
emit,
map,
emit: None,
map: None,
})
} else {
None
Expand Down Expand Up @@ -176,102 +93,15 @@ impl Loader for FetchCacher {
}
}

impl Cacher for FetchCacher {
fn get(
&self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
) -> Option<String> {
let extension = match cache_type {
CacheType::Declaration => "d.ts",
CacheType::Emit => "js",
CacheType::SourceMap => "js.map",
CacheType::TypeScriptBuildInfo => "buildinfo",
CacheType::Version => {
return self.get_emit_metadata(specifier).map(|d| d.version_hash)
}
};
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, extension)?;
self
.disk_cache
.get(&filename)
.ok()
.map(|b| String::from_utf8(b).ok())
.flatten()
}

fn set(
&mut self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
value: String,
) -> Result<(), AnyError> {
let extension = match cache_type {
CacheType::Declaration => "d.ts",
CacheType::Emit => "js",
CacheType::SourceMap => "js.map",
CacheType::TypeScriptBuildInfo => "buildinfo",
CacheType::Version => {
let data = if let Some(mut data) = self.get_emit_metadata(specifier) {
data.version_hash = value;
data
} else {
EmitMetadata {
version_hash: value,
}
};
return self.set_emit_metadata(specifier, data);
}
};
let filename = self
.disk_cache
.get_cache_filename_with_extension(specifier, extension)
.unwrap();
self
.disk_cache
.set(&filename, value.as_bytes())
.map_err(|e| e.into())
}
}

impl CacherLoader for FetchCacher {
fn as_cacher(&self) -> &dyn Cacher {
self
}

fn as_mut_loader(&mut self) -> &mut dyn Loader {
self
}

fn as_mut_cacher(&mut self) -> &mut dyn Cacher {
self
}
}

/// An in memory cache that is used by the runtime `Deno.emit()` API to provide
/// the same behavior as the disk cache when sources are provided.
/// An in memory cache that is used by the runtime `Deno.emit()` API.
#[derive(Debug)]
pub(crate) struct MemoryCacher {
sources: HashMap<String, Arc<String>>,
declarations: HashMap<ModuleSpecifier, String>,
emits: HashMap<ModuleSpecifier, String>,
maps: HashMap<ModuleSpecifier, String>,
build_infos: HashMap<ModuleSpecifier, String>,
versions: HashMap<ModuleSpecifier, String>,
}

impl MemoryCacher {
pub fn new(sources: HashMap<String, Arc<String>>) -> Self {
Self {
sources,
declarations: HashMap::default(),
emits: HashMap::default(),
maps: HashMap::default(),
build_infos: HashMap::default(),
versions: HashMap::default(),
}
Self { sources }
}
}

Expand All @@ -296,55 +126,3 @@ impl Loader for MemoryCacher {
Box::pin(future::ready(Ok(response)))
}
}

impl Cacher for MemoryCacher {
fn get(
&self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
) -> Option<String> {
match cache_type {
CacheType::Declaration => self.declarations.get(specifier).cloned(),
CacheType::Emit => self.emits.get(specifier).cloned(),
CacheType::SourceMap => self.maps.get(specifier).cloned(),
CacheType::TypeScriptBuildInfo => {
self.build_infos.get(specifier).cloned()
}
CacheType::Version => self.versions.get(specifier).cloned(),
}
}

fn set(
&mut self,
cache_type: CacheType,
specifier: &ModuleSpecifier,
value: String,
) -> Result<(), AnyError> {
match cache_type {
CacheType::Declaration => {
self.declarations.insert(specifier.clone(), value)
}
CacheType::Emit => self.emits.insert(specifier.clone(), value),
CacheType::SourceMap => self.maps.insert(specifier.clone(), value),
CacheType::TypeScriptBuildInfo => {
self.build_infos.insert(specifier.clone(), value)
}
CacheType::Version => self.versions.insert(specifier.clone(), value),
};
Ok(())
}
}

impl CacherLoader for MemoryCacher {
fn as_cacher(&self) -> &dyn Cacher {
self
}

fn as_mut_loader(&mut self) -> &mut dyn Loader {
self
}

fn as_mut_cacher(&mut self) -> &mut dyn Cacher {
self
}
}
23 changes: 12 additions & 11 deletions cli/deno_dir.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.

use crate::disk_cache::DiskCache;
use std::path::PathBuf;

/// `DenoDir` serves as coordinator for multiple `DiskCache`s containing them
Expand All @@ -9,8 +8,6 @@ use std::path::PathBuf;
pub struct DenoDir {
/// Example: /Users/rld/.deno/
pub root: PathBuf,
/// Used by TsCompiler to cache compiler output.
pub gen_cache: DiskCache,
}

impl DenoDir {
Expand All @@ -33,15 +30,19 @@ impl DenoDir {
panic!("Could not set the Deno root directory")
};
assert!(root.is_absolute());
let gen_path = root.join("gen");

let deno_dir = Self {
root,
gen_cache: DiskCache::new(&gen_path),
};
deno_dir.gen_cache.ensure_dir_exists(&gen_path)?;
std::fs::create_dir_all(&root).map_err(|e| {
std::io::Error::new(e.kind(), format!(
"Could not create deno dir compiler cache location: {:?}\nCheck the permission of the directory.",
root
))
})?;
Ok(Self { root })
}

Ok(deno_dir)
/// File path to the emit cache database file.
pub fn emit_cache_db_file_path(&self) -> PathBuf {
// bump this version name to invalidate the entire emit cache
self.root.join("emit_cache_v1")
}
}

Expand Down
Loading