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

perf: v8 code cache (#23081)

This PR enables V8 code cache for ES modules and for `require` scripts
through `op_eval_context`. Code cache artifacts are transparently stored
and fetched using sqlite db and are passed to V8. `--no-code-cache` can
be used to disable.

---------

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
Igor Zinkovsky 2024-04-17 07:19:55 -07:00 committed by GitHub
parent 9acbf90b06
commit b3d7df5535
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 889 additions and 76 deletions

1
Cargo.lock generated
View file

@ -1836,6 +1836,7 @@ dependencies = [
"notify",
"ntapi",
"once_cell",
"percent-encoding",
"regex",
"ring",
"rustyline",

View file

@ -517,6 +517,7 @@ pub struct Flags {
pub unstable_config: UnstableConfig,
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
pub v8_flags: Vec<String>,
pub code_cache_enabled: bool,
}
fn join_paths(allowlist: &[String], d: &str) -> String {
@ -2236,6 +2237,7 @@ fn run_subcommand() -> Command {
.trailing_var_arg(true),
)
.arg(env_file_arg())
.arg(no_code_cache_arg())
.about("Run a JavaScript or TypeScript program")
.long_about(
"Run a JavaScript or TypeScript program
@ -3222,6 +3224,13 @@ fn no_clear_screen_arg() -> Arg {
.help("Do not clear terminal screen when under watch mode")
}
fn no_code_cache_arg() -> Arg {
Arg::new("no-code-cache")
.long("no-code-cache")
.help("Disable V8 code cache feature")
.action(ArgAction::SetTrue)
}
fn watch_exclude_arg() -> Arg {
Arg::new("watch-exclude")
.long("watch-exclude")
@ -3829,6 +3838,8 @@ fn run_parse(
) -> clap::error::Result<()> {
runtime_args_parse(flags, matches, true, true);
flags.code_cache_enabled = !matches.get_flag("no-code-cache");
let mut script_arg =
matches.remove_many::<String>("script_arg").ok_or_else(|| {
let mut app = app;
@ -4469,6 +4480,7 @@ mod tests {
..Default::default()
},
log_level: Some(Level::Error),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4540,6 +4552,7 @@ mod tests {
"script.ts".to_string()
)),
reload: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -4561,6 +4574,7 @@ mod tests {
exclude: vec![],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4585,6 +4599,7 @@ mod tests {
exclude: vec![],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4609,6 +4624,7 @@ mod tests {
exclude: vec![],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4633,6 +4649,7 @@ mod tests {
exclude: vec![],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4659,6 +4676,7 @@ mod tests {
exclude: vec![],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4687,6 +4705,7 @@ mod tests {
exclude: vec![],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4715,6 +4734,7 @@ mod tests {
exclude: vec![String::from("foo")],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4739,6 +4759,7 @@ mod tests {
exclude: vec![String::from("bar")],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4764,6 +4785,7 @@ mod tests {
exclude: vec![String::from("foo"), String::from("bar")],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4789,6 +4811,7 @@ mod tests {
exclude: vec![String::from("baz"), String::from("qux"),],
}),
}),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4806,6 +4829,7 @@ mod tests {
"script.ts".to_string()
)),
allow_write: Some(vec![]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4819,6 +4843,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Run(RunFlags::new_default("_".to_string())),
v8_flags: svec!["--help"],
code_cache_enabled: true,
..Flags::default()
}
);
@ -4836,6 +4861,7 @@ mod tests {
"script.ts".to_string()
)),
v8_flags: svec!["--expose-gc", "--gc-stats=1"],
code_cache_enabled: true,
..Flags::default()
}
);
@ -4889,6 +4915,7 @@ mod tests {
)),
argv: svec!["--title", "X"],
allow_net: Some(vec![]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4912,6 +4939,7 @@ mod tests {
allow_write: Some(vec![]),
allow_ffi: Some(vec![]),
allow_hrtime: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -4927,6 +4955,7 @@ mod tests {
"gist.ts".to_string()
)),
allow_read: Some(vec![]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4942,6 +4971,7 @@ mod tests {
"gist.ts".to_string()
)),
deny_read: Some(vec![]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -4957,6 +4987,7 @@ mod tests {
"gist.ts".to_string(),
)),
allow_hrtime: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -4972,6 +5003,7 @@ mod tests {
"gist.ts".to_string(),
)),
deny_hrtime: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -4999,6 +5031,7 @@ mod tests {
)),
argv: svec!["--", "-D", "--allow-net"],
allow_write: Some(vec![]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -5713,6 +5746,7 @@ mod tests {
"script.ts".to_string(),
)),
config_flag: ConfigFlag::Path("tsconfig.json".to_owned()),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6007,6 +6041,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
"script.ts".to_string(),
)),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6031,6 +6066,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
"script.ts".to_string(),
)),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6055,6 +6091,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
"script.ts".to_string(),
)),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6079,6 +6116,7 @@ mod tests {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
"script.ts".to_string(),
)),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6099,6 +6137,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_net: Some(svec!["127.0.0.1"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6115,6 +6154,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_net: Some(svec!["127.0.0.1"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6131,6 +6171,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_env: Some(svec!["HOME"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6147,6 +6188,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_env: Some(svec!["HOME"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6167,6 +6209,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_env: Some(svec!["HOME", "PATH"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6183,6 +6226,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_env: Some(svec!["HOME", "PATH"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6224,6 +6268,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_sys: Some(vec![]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6239,6 +6284,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_sys: Some(vec![]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6255,6 +6301,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_sys: Some(svec!["hostname"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6271,6 +6318,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_sys: Some(svec!["hostname"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6291,6 +6339,7 @@ mod tests {
"script.ts".to_string(),
)),
allow_sys: Some(svec!["hostname", "osRelease"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6311,6 +6360,7 @@ mod tests {
"script.ts".to_string(),
)),
deny_sys: Some(svec!["hostname", "osRelease"]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6618,6 +6668,7 @@ mod tests {
"script.ts".to_string(),
)),
import_map_path: Some("import_map.json".to_owned()),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6699,6 +6750,22 @@ mod tests {
"script.ts".to_string(),
)),
env_file: Some(".env".to_owned()),
code_cache_enabled: true,
..Flags::default()
}
);
}
#[test]
fn run_no_code_cache() {
let r =
flags_from_vec(svec!["deno", "run", "--no-code-cache", "script.ts"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
"script.ts".to_string(),
)),
..Flags::default()
}
);
@ -6715,6 +6782,7 @@ mod tests {
"script.ts".to_string(),
)),
env_file: Some(".another_env".to_owned()),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6746,6 +6814,7 @@ mod tests {
)),
seed: Some(250_u64),
v8_flags: svec!["--random-seed=250"],
code_cache_enabled: true,
..Flags::default()
}
);
@ -6769,6 +6838,7 @@ mod tests {
)),
seed: Some(250_u64),
v8_flags: svec!["--expose-gc", "--random-seed=250"],
code_cache_enabled: true,
..Flags::default()
}
);
@ -6907,6 +6977,7 @@ mod tests {
"script.ts".to_string(),
)),
log_level: Some(Level::Debug),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6922,6 +6993,7 @@ mod tests {
"script.ts".to_string(),
)),
log_level: Some(Level::Error),
code_cache_enabled: true,
..Flags::default()
}
);
@ -6955,6 +7027,7 @@ mod tests {
"script.ts".to_string(),
)),
argv: svec!["--allow-read", "--allow-net"],
code_cache_enabled: true,
..Flags::default()
}
);
@ -6980,6 +7053,7 @@ mod tests {
location: Some(Url::parse("https://foo/").unwrap()),
allow_read: Some(vec![]),
argv: svec!["--allow-net", "-r", "--help", "--foo", "bar"],
code_cache_enabled: true,
..Flags::default()
}
);
@ -6992,6 +7066,7 @@ mod tests {
"script.ts".to_string(),
)),
argv: svec!["foo", "bar"],
code_cache_enabled: true,
..Flags::default()
}
);
@ -7003,6 +7078,7 @@ mod tests {
"script.ts".to_string(),
)),
argv: svec!["-"],
code_cache_enabled: true,
..Flags::default()
}
);
@ -7016,6 +7092,7 @@ mod tests {
"script.ts".to_string(),
)),
argv: svec!["-", "foo", "bar"],
code_cache_enabled: true,
..Flags::default()
}
);
@ -7031,6 +7108,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::None,
code_cache_enabled: true,
..Flags::default()
}
);
@ -7047,6 +7125,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::Local,
code_cache_enabled: true,
..Flags::default()
}
);
@ -7091,6 +7170,7 @@ mod tests {
"script.ts".to_string(),
)),
unsafely_ignore_certificate_errors: Some(vec![]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7118,6 +7198,7 @@ mod tests {
"[::1]",
"1.2.3.4"
]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7161,6 +7242,7 @@ mod tests {
"script.ts".to_string(),
)),
no_remote: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -7176,6 +7258,7 @@ mod tests {
"script.ts".to_string(),
)),
no_npm: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -7192,6 +7275,7 @@ mod tests {
"script.ts".to_string(),
)),
node_modules_dir: Some(true),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7209,6 +7293,7 @@ mod tests {
"script.ts".to_string(),
)),
node_modules_dir: Some(false),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7224,6 +7309,7 @@ mod tests {
"script.ts".to_string(),
)),
vendor: Some(true),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7236,6 +7322,7 @@ mod tests {
"script.ts".to_string(),
)),
vendor: Some(false),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7251,6 +7338,7 @@ mod tests {
"script.ts".to_string(),
)),
cached_only: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -7279,6 +7367,7 @@ mod tests {
"127.0.0.1:4545",
"localhost:4545"
]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7307,6 +7396,7 @@ mod tests {
"127.0.0.1:4545",
"localhost:4545"
]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7338,6 +7428,7 @@ mod tests {
"localhost:5678",
"[::1]:8080"
]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7369,6 +7460,7 @@ mod tests {
"localhost:5678",
"[::1]:8080"
]),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7391,6 +7483,7 @@ mod tests {
)),
lock_write: true,
lock: Some(String::from("lock.json")),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7403,6 +7496,7 @@ mod tests {
"script.ts".to_string(),
)),
no_lock: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -7422,6 +7516,7 @@ mod tests {
)),
lock_write: true,
lock: Some(String::from("./deno.lock")),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7442,6 +7537,7 @@ mod tests {
)),
lock_write: true,
lock: Some(String::from("lock.json")),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7454,6 +7550,7 @@ mod tests {
"script.ts".to_string(),
)),
lock_write: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -7546,6 +7643,7 @@ mod tests {
"script.ts".to_string(),
)),
ca_data: Some(CaData::File("example.crt".to_owned())),
code_cache_enabled: true,
..Flags::default()
}
);
@ -7566,6 +7664,7 @@ mod tests {
"script.ts".to_string(),
)),
enable_testing_features: true,
code_cache_enabled: true,
..Flags::default()
}
);
@ -8244,6 +8343,7 @@ mod tests {
"foo.js".to_string(),
)),
inspect: Some("127.0.0.1:9229".parse().unwrap()),
code_cache_enabled: true,
..Flags::default()
}
);
@ -8259,6 +8359,7 @@ mod tests {
"foo.js".to_string(),
)),
inspect_wait: Some("127.0.0.1:9229".parse().unwrap()),
code_cache_enabled: true,
..Flags::default()
}
);
@ -8276,6 +8377,7 @@ mod tests {
"foo.js".to_string(),
)),
inspect_wait: Some("127.0.0.1:3567".parse().unwrap()),
code_cache_enabled: true,
..Flags::default()
}
);
@ -8802,6 +8904,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::Local,
code_cache_enabled: true,
..Flags::default()
}
);
@ -8814,6 +8917,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::All,
code_cache_enabled: true,
..Flags::default()
}
);
@ -8826,6 +8930,7 @@ mod tests {
"script.ts".to_string(),
)),
type_check_mode: TypeCheckMode::None,
code_cache_enabled: true,
..Flags::default()
}
);
@ -8850,6 +8955,7 @@ mod tests {
"script.ts".to_string(),
)),
config_flag: ConfigFlag::Disabled,
code_cache_enabled: true,
..Flags::default()
}
);

View file

@ -1653,6 +1653,10 @@ impl CliOptions {
&self.flags.v8_flags
}
pub fn code_cache_enabled(&self) -> bool {
self.flags.code_cache_enabled
}
pub fn watch_paths(&self) -> Vec<PathBuf> {
let mut full_paths = Vec::new();
if let DenoSubcommand::Run(RunFlags {

15
cli/cache/caches.rs vendored
View file

@ -8,6 +8,7 @@ use once_cell::sync::OnceCell;
use super::cache_db::CacheDB;
use super::cache_db::CacheDBConfiguration;
use super::check::TYPE_CHECK_CACHE_DB;
use super::code_cache::CODE_CACHE_DB;
use super::deno_dir::DenoDirProvider;
use super::fast_check::FAST_CHECK_CACHE_DB;
use super::incremental::INCREMENTAL_CACHE_DB;
@ -22,6 +23,7 @@ pub struct Caches {
fast_check_db: OnceCell<CacheDB>,
node_analysis_db: OnceCell<CacheDB>,
type_checking_cache_db: OnceCell<CacheDB>,
code_cache_db: OnceCell<CacheDB>,
}
impl Caches {
@ -34,6 +36,7 @@ impl Caches {
fast_check_db: Default::default(),
node_analysis_db: Default::default(),
type_checking_cache_db: Default::default(),
code_cache_db: Default::default(),
}
}
@ -124,4 +127,16 @@ impl Caches {
.map(|dir| dir.type_checking_cache_db_file_path()),
)
}
pub fn code_cache_db(&self) -> CacheDB {
Self::make_db(
&self.code_cache_db,
&CODE_CACHE_DB,
self
.dir_provider
.get_or_create()
.ok()
.map(|dir| dir.code_cache_db_file_path()),
)
}
}

231
cli/cache/code_cache.rs vendored Normal file
View file

@ -0,0 +1,231 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
use deno_runtime::code_cache;
use deno_runtime::deno_webstorage::rusqlite::params;
use super::cache_db::CacheDB;
use super::cache_db::CacheDBConfiguration;
use super::cache_db::CacheFailure;
pub static CODE_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration {
table_initializer: "CREATE TABLE IF NOT EXISTS codecache (
specifier TEXT NOT NULL,
type TEXT NOT NULL,
source_hash TEXT NOT NULL,
data BLOB NOT NULL,
PRIMARY KEY (specifier, type)
);",
on_version_change: "DELETE FROM codecache;",
preheat_queries: &[],
on_failure: CacheFailure::Blackhole,
};
#[derive(Clone)]
pub struct CodeCache {
inner: CodeCacheInner,
}
impl CodeCache {
pub fn new(db: CacheDB) -> Self {
Self {
inner: CodeCacheInner::new(db),
}
}
fn ensure_ok<T: Default>(res: Result<T, AnyError>) -> T {
match res {
Ok(x) => x,
Err(err) => {
// TODO(mmastrac): This behavior was inherited from before the refactoring but it probably makes sense to move it into the cache
// at some point.
// should never error here, but if it ever does don't fail
if cfg!(debug_assertions) {
panic!("Error using code cache: {err:#}");
} else {
log::debug!("Error using code cache: {:#}", err);
}
T::default()
}
}
}
pub fn get_sync(
&self,
specifier: &str,
code_cache_type: code_cache::CodeCacheType,
source_hash: &str,
) -> Option<Vec<u8>> {
Self::ensure_ok(self.inner.get_sync(
specifier,
code_cache_type,
source_hash,
))
}
pub fn set_sync(
&self,
specifier: &str,
code_cache_type: code_cache::CodeCacheType,
source_hash: &str,
data: &[u8],
) {
Self::ensure_ok(self.inner.set_sync(
specifier,
code_cache_type,
source_hash,
data,
));
}
}
impl code_cache::CodeCache for CodeCache {
fn get_sync(
&self,
specifier: &str,
code_cache_type: code_cache::CodeCacheType,
source_hash: &str,
) -> Option<Vec<u8>> {
self.get_sync(specifier, code_cache_type, source_hash)
}
fn set_sync(
&self,
specifier: &str,
code_cache_type: code_cache::CodeCacheType,
source_hash: &str,
data: &[u8],
) {
self.set_sync(specifier, code_cache_type, source_hash, data);
}
}
#[derive(Clone)]
struct CodeCacheInner {
conn: CacheDB,
}
impl CodeCacheInner {
pub fn new(conn: CacheDB) -> Self {
Self { conn }
}
pub fn get_sync(
&self,
specifier: &str,
code_cache_type: code_cache::CodeCacheType,
source_hash: &str,
) -> Result<Option<Vec<u8>>, AnyError> {
let query = "
SELECT
data
FROM
codecache
WHERE
specifier=?1 AND type=?2 AND source_hash=?3
LIMIT 1";
let params = params![specifier, code_cache_type.as_str(), source_hash,];
self.conn.query_row(query, params, |row| {
let value: Vec<u8> = row.get(0)?;
Ok(value)
})
}
pub fn set_sync(
&self,
specifier: &str,
code_cache_type: code_cache::CodeCacheType,
source_hash: &str,
data: &[u8],
) -> Result<(), AnyError> {
let sql = "
INSERT OR REPLACE INTO
codecache (specifier, type, source_hash, data)
VALUES
(?1, ?2, ?3, ?4)";
let params =
params![specifier, code_cache_type.as_str(), source_hash, data];
self.conn.execute(sql, params)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn end_to_end() {
let conn = CacheDB::in_memory(&CODE_CACHE_DB, "1.0.0");
let cache = CodeCacheInner::new(conn);
assert!(cache
.get_sync(
"file:///foo/bar.js",
code_cache::CodeCacheType::EsModule,
"hash",
)
.unwrap()
.is_none());
let data_esm = vec![1, 2, 3];
cache
.set_sync(
"file:///foo/bar.js",
code_cache::CodeCacheType::EsModule,
"hash",
&data_esm,
)
.unwrap();
assert_eq!(
cache
.get_sync(
"file:///foo/bar.js",
code_cache::CodeCacheType::EsModule,
"hash",
)
.unwrap()
.unwrap(),
data_esm
);
assert!(cache
.get_sync(
"file:///foo/bar.js",
code_cache::CodeCacheType::Script,
"hash",
)
.unwrap()
.is_none());
let data_script = vec![4, 5, 6];
cache
.set_sync(
"file:///foo/bar.js",
code_cache::CodeCacheType::Script,
"hash",
&data_script,
)
.unwrap();
assert_eq!(
cache
.get_sync(
"file:///foo/bar.js",
code_cache::CodeCacheType::Script,
"hash",
)
.unwrap()
.unwrap(),
data_script
);
assert_eq!(
cache
.get_sync(
"file:///foo/bar.js",
code_cache::CodeCacheType::EsModule,
"hash",
)
.unwrap()
.unwrap(),
data_esm
);
}
}

View file

@ -142,6 +142,12 @@ impl DenoDir {
self.root.join("npm")
}
/// Path for the V8 code cache.
pub fn code_cache_db_file_path(&self) -> PathBuf {
// bump this version name to invalidate the entire cache
self.root.join("v8_code_cache_v1")
}
/// Path used for the REPL history file.
/// Can be overridden or disabled by setting `DENO_REPL_HISTORY` environment variable.
pub fn repl_history_file_path(&self) -> Option<PathBuf> {

2
cli/cache/mod.rs vendored
View file

@ -25,6 +25,7 @@ use std::time::SystemTime;
mod cache_db;
mod caches;
mod check;
mod code_cache;
mod common;
mod deno_dir;
mod disk_cache;
@ -37,6 +38,7 @@ mod parsed_source;
pub use caches::Caches;
pub use check::TypeCheckCache;
pub use code_cache::CodeCache;
pub use common::FastInsecureHasher;
pub use deno_dir::DenoDir;
pub use deno_dir::DenoDirProvider;

View file

@ -39,6 +39,7 @@ pub static MODULE_INFO_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration {
on_failure: CacheFailure::InMemory,
};
#[derive(Debug)]
pub struct ModuleInfoCacheSourceHash(String);
impl ModuleInfoCacheSourceHash {
@ -55,6 +56,12 @@ impl ModuleInfoCacheSourceHash {
}
}
impl From<ModuleInfoCacheSourceHash> for String {
fn from(source_hash: ModuleInfoCacheSourceHash) -> String {
source_hash.0
}
}
/// A cache of `deno_graph::ModuleInfo` objects. Using this leads to a considerable
/// performance improvement because when it exists we can skip parsing a module for
/// deno_graph.
@ -80,6 +87,23 @@ impl ModuleInfoCache {
}
}
pub fn get_module_source_hash(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
) -> Result<Option<ModuleInfoCacheSourceHash>, AnyError> {
let query = "SELECT source_hash FROM moduleinfocache WHERE specifier=?1 AND media_type=?2";
let res = self.conn.query_row(
query,
params![specifier.as_str(), serialize_media_type(media_type)],
|row| {
let source_hash: String = row.get(0)?;
Ok(ModuleInfoCacheSourceHash(source_hash))
},
)?;
Ok(res)
}
pub fn get_module_info(
&self,
specifier: &ModuleSpecifier,

View file

@ -9,6 +9,7 @@ use crate::args::PackageJsonDepsProvider;
use crate::args::StorageKeyResolver;
use crate::args::TsConfigType;
use crate::cache::Caches;
use crate::cache::CodeCache;
use crate::cache::DenoDir;
use crate::cache::DenoDirProvider;
use crate::cache::EmitCache;
@ -178,6 +179,7 @@ struct CliFactoryServices {
cjs_resolutions: Deferred<Arc<CjsResolutionStore>>,
cli_node_resolver: Deferred<Arc<CliNodeResolver>>,
feature_checker: Deferred<Arc<FeatureChecker>>,
code_cache: Deferred<Arc<CodeCache>>,
}
pub struct CliFactory {
@ -226,6 +228,9 @@ impl CliFactory {
_ = caches.fast_check_db();
_ = caches.type_checking_cache_db();
}
if self.options.code_cache_enabled() {
_ = caches.code_cache_db();
}
}
_ => {}
}
@ -534,6 +539,12 @@ impl CliFactory {
})
}
pub fn code_cache(&self) -> Result<&Arc<CodeCache>, AnyError> {
self.services.code_cache.get_or_try_init(|| {
Ok(Arc::new(CodeCache::new(self.caches()?.code_cache_db())))
})
}
pub fn parsed_source_cache(&self) -> &Arc<ParsedSourceCache> {
self
.services
@ -790,6 +801,12 @@ impl CliFactory {
fs.clone(),
cli_node_resolver.clone(),
),
if self.options.code_cache_enabled() {
Some(self.code_cache()?.clone())
} else {
None
},
self.module_info_cache()?.clone(),
)),
self.root_cert_store_provider().clone(),
self.fs().clone(),
@ -804,6 +821,11 @@ impl CliFactory {
// self.options.disable_deprecated_api_warning,
true,
self.options.verbose_deprecated_api_warning,
if self.options.code_cache_enabled() {
Some(self.code_cache()?.clone())
} else {
None
},
))
}

View file

@ -18,9 +18,9 @@ use crate::tools::check;
use crate::tools::check::TypeChecker;
use crate::util::file_watcher::WatcherCommunicator;
use crate::util::fs::canonicalize_path;
use crate::util::path::specifier_to_file_path;
use crate::util::sync::TaskQueue;
use crate::util::sync::TaskQueuePermit;
use deno_runtime::fs_util::specifier_to_file_path;
use deno_config::WorkspaceMemberConfig;
use deno_core::anyhow::bail;

View file

@ -10,7 +10,7 @@ use crate::args::jsr_url;
use crate::npm::CliNpmResolver;
use crate::resolver::CliNodeResolver;
use crate::tools::lint::create_linter;
use crate::util::path::specifier_to_file_path;
use deno_runtime::fs_util::specifier_to_file_path;
use deno_ast::SourceRange;
use deno_ast::SourceRangedForSpanned;

View file

@ -1,7 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::cache::HttpCache;
use crate::util::path::specifier_to_file_path;
use deno_runtime::fs_util::specifier_to_file_path;
use deno_core::parking_lot::Mutex;
use deno_core::ModuleSpecifier;

View file

@ -15,7 +15,7 @@ use super::tsc;
use crate::jsr::JsrFetchResolver;
use crate::util::path::is_importable_ext;
use crate::util::path::relative_specifier;
use crate::util::path::specifier_to_file_path;
use deno_runtime::fs_util::specifier_to_file_path;
use deno_ast::LineAndColumnIndex;
use deno_ast::SourceTextInfo;

View file

@ -10,7 +10,6 @@ use crate::lsp::logging::lsp_warn;
use crate::tools::lint::get_configured_rules;
use crate::tools::lint::ConfiguredRules;
use crate::util::fs::canonicalize_path_maybe_not_exists;
use crate::util::path::specifier_to_file_path;
use deno_ast::MediaType;
use deno_config::FmtOptionsConfig;
use deno_config::TsConfig;
@ -25,6 +24,7 @@ use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use deno_lockfile::Lockfile;
use deno_runtime::deno_node::PackageJson;
use deno_runtime::fs_util::specifier_to_file_path;
use deno_runtime::permissions::PermissionsContainer;
use import_map::ImportMap;
use lsp::Url;

View file

@ -20,7 +20,7 @@ use crate::resolver::CliNodeResolver;
use crate::resolver::SloppyImportsFsEntry;
use crate::resolver::SloppyImportsResolution;
use crate::resolver::SloppyImportsResolver;
use crate::util::path::specifier_to_file_path;
use deno_runtime::fs_util::specifier_to_file_path;
use dashmap::DashMap;
use deno_ast::MediaType;

View file

@ -120,10 +120,10 @@ use crate::tools::upgrade::check_for_upgrades_for_lsp;
use crate::tools::upgrade::upgrade_check_enabled;
use crate::util::fs::remove_dir_all_if_exists;
use crate::util::path::is_importable_ext;
use crate::util::path::specifier_to_file_path;
use crate::util::path::to_percent_decoded_str;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
use deno_runtime::fs_util::specifier_to_file_path;
struct LspRootCertStoreProvider(RootCertStore);

View file

@ -30,8 +30,8 @@ use crate::tsc;
use crate::tsc::ResolveArgs;
use crate::tsc::MISSING_DEPENDENCY_SPECIFIER;
use crate::util::path::relative_specifier;
use crate::util::path::specifier_to_file_path;
use crate::util::path::to_percent_decoded_str;
use deno_runtime::fs_util::specifier_to_file_path;
use dashmap::DashMap;
use deno_ast::MediaType;

View file

@ -4,6 +4,8 @@ use crate::args::jsr_url;
use crate::args::CliOptions;
use crate::args::DenoSubcommand;
use crate::args::TsTypeLib;
use crate::cache::CodeCache;
use crate::cache::ModuleInfoCache;
use crate::cache::ParsedSourceCache;
use crate::emit::Emitter;
use crate::graph_util::graph_lock_or_exit;
@ -50,7 +52,9 @@ use deno_graph::JsonModule;
use deno_graph::Module;
use deno_graph::Resolution;
use deno_lockfile::Lockfile;
use deno_runtime::code_cache;
use deno_runtime::deno_node::NodeResolutionMode;
use deno_runtime::fs_util::code_timestamp;
use deno_runtime::permissions::PermissionsContainer;
use deno_semver::npm::NpmPackageReqReference;
use deno_terminal::colors;
@ -311,6 +315,8 @@ struct SharedCliModuleLoaderState {
resolver: Arc<CliGraphResolver>,
node_resolver: Arc<CliNodeResolver>,
npm_module_loader: NpmModuleLoader,
code_cache: Option<Arc<CodeCache>>,
module_info_cache: Arc<ModuleInfoCache>,
}
pub struct CliModuleLoaderFactory {
@ -328,6 +334,8 @@ impl CliModuleLoaderFactory {
resolver: Arc<CliGraphResolver>,
node_resolver: Arc<CliNodeResolver>,
npm_module_loader: NpmModuleLoader,
code_cache: Option<Arc<CodeCache>>,
module_info_cache: Arc<ModuleInfoCache>,
) -> Self {
Self {
shared: Arc::new(SharedCliModuleLoaderState {
@ -348,6 +356,8 @@ impl CliModuleLoaderFactory {
resolver,
node_resolver,
npm_module_loader,
code_cache,
module_info_cache,
}),
}
}
@ -458,12 +468,40 @@ impl CliModuleLoader {
return Err(generic_error("Attempted to load JSON module without specifying \"type\": \"json\" attribute in the import statement."));
}
let code_cache = if module_type == ModuleType::JavaScript {
self.shared.code_cache.as_ref().and_then(|cache| {
let code_hash = self
.get_code_hash_or_timestamp(specifier, code_source.media_type)
.ok()
.flatten();
if let Some(code_hash) = code_hash {
cache
.get_sync(
specifier.as_str(),
code_cache::CodeCacheType::EsModule,
&code_hash,
)
.map(Cow::from)
.inspect(|_| {
// This log line is also used by tests.
log::debug!(
"V8 code cache hit for ES module: {specifier}, [{code_hash:?}]"
);
})
} else {
None
}
})
} else {
None
};
Ok(ModuleSource::new_with_redirect(
module_type,
ModuleSourceCode::String(code),
specifier,
&code_source.found_url,
None,
code_cache,
))
}
@ -603,6 +641,25 @@ impl CliModuleLoader {
resolution.map_err(|err| err.into())
}
fn get_code_hash_or_timestamp(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
) -> Result<Option<String>, AnyError> {
let hash = self
.shared
.module_info_cache
.get_module_source_hash(specifier, media_type)?;
if let Some(hash) = hash {
return Ok(Some(hash.into()));
}
// Use the modified timestamp from the local file system if we don't have a hash.
let timestamp = code_timestamp(specifier.as_str())
.map(|timestamp| timestamp.to_string())?;
Ok(Some(timestamp))
}
}
impl ModuleLoader for CliModuleLoader {
@ -678,6 +735,33 @@ impl ModuleLoader for CliModuleLoader {
}
.boxed_local()
}
fn code_cache_ready(
&self,
specifier: &ModuleSpecifier,
code_cache: &[u8],
) -> Pin<Box<dyn Future<Output = ()>>> {
if let Some(cache) = self.shared.code_cache.as_ref() {
let media_type = MediaType::from_specifier(specifier);
let code_hash = self
.get_code_hash_or_timestamp(specifier, media_type)
.ok()
.flatten();
if let Some(code_hash) = code_hash {
// This log line is also used by tests.
log::debug!(
"Updating V8 code cache for ES module: {specifier}, [{code_hash:?}]"
);
cache.set_sync(
specifier.as_str(),
code_cache::CodeCacheType::EsModule,
&code_hash,
code_cache,
);
}
}
async {}.boxed_local()
}
}
struct CliSourceMapGetter {

View file

@ -21,7 +21,7 @@ use crate::args::package_json::get_local_package_json_version_reqs;
use crate::args::NpmProcessState;
use crate::args::NpmProcessStateKind;
use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs;
use crate::util::path::specifier_to_file_path;
use deno_runtime::fs_util::specifier_to_file_path;
use super::common::types_package_name;
use super::CliNpmResolver;

View file

@ -27,6 +27,7 @@ use deno_runtime::deno_node::NodeResolutionMode;
use deno_runtime::deno_node::NodeResolver;
use deno_runtime::deno_node::NpmResolver as DenoNodeNpmResolver;
use deno_runtime::deno_node::PackageJson;
use deno_runtime::fs_util::specifier_to_file_path;
use deno_runtime::permissions::PermissionsContainer;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq;
@ -48,7 +49,6 @@ use crate::node::CliNodeCodeTranslator;
use crate::npm::ByonmCliNpmResolver;
use crate::npm::CliNpmResolver;
use crate::npm::InnerCliNpmResolverRef;
use crate::util::path::specifier_to_file_path;
use crate::util::sync::AtomicFlag;
pub fn format_range_with_colors(range: &deno_graph::Range) -> String {

View file

@ -572,6 +572,8 @@ pub async fn run(
// metadata.disable_deprecated_api_warning,
true,
false,
// Code cache is not supported for standalone binary yet.
None,
);
// Initialize v8 once from the main thread.

View file

@ -24,7 +24,7 @@ use crate::tools::fmt::format_json;
use crate::util::fs::canonicalize_path;
use crate::util::fs::resolve_from_cwd;
use crate::util::path::relative_specifier;
use crate::util::path::specifier_to_file_path;
use deno_runtime::fs_util::specifier_to_file_path;
mod analyze;
mod build;

View file

@ -9,8 +9,6 @@ use deno_ast::ModuleSpecifier;
use deno_config::glob::PathGlobMatch;
use deno_config::glob::PathOrPattern;
use deno_config::glob::PathOrPatternSet;
use deno_core::error::uri_error;
use deno_core::error::AnyError;
/// Checks if the path has an extension Deno supports for script execution.
pub fn is_script_ext(path: &Path) -> bool {
@ -82,49 +80,6 @@ pub fn mapped_specifier_for_tsc(
}
}
/// Attempts to convert a specifier to a file path. By default, uses the Url
/// crate's `to_file_path()` method, but falls back to try and resolve unix-style
/// paths on Windows.
pub fn specifier_to_file_path(
specifier: &ModuleSpecifier,
) -> Result<PathBuf, AnyError> {
let result = if specifier.scheme() != "file" {
Err(())
} else if cfg!(windows) {
match specifier.to_file_path() {
Ok(path) => Ok(path),
Err(()) => {
// This might be a unix-style path which is used in the tests even on Windows.
// Attempt to see if we can convert it to a `PathBuf`. This code should be removed
// once/if https://github.com/servo/rust-url/issues/730 is implemented.
if specifier.scheme() == "file"
&& specifier.host().is_none()
&& specifier.port().is_none()
&& specifier.path_segments().is_some()
{
let path_str = specifier.path();
match String::from_utf8(
percent_encoding::percent_decode(path_str.as_bytes()).collect(),
) {
Ok(path_str) => Ok(PathBuf::from(path_str)),
Err(_) => Err(()),
}
} else {
Err(())
}
}
}
} else {
specifier.to_file_path()
};
match result {
Ok(path) => Ok(path),
Err(()) => Err(uri_error(format!(
"Invalid file path.\n Specifier: {specifier}"
))),
}
}
/// `from.make_relative(to)` but with fixes.
pub fn relative_specifier(
from: &ModuleSpecifier,
@ -330,24 +285,6 @@ mod test {
assert!(!is_importable_ext(Path::new("foo.mjsx")));
}
#[test]
fn test_specifier_to_file_path() {
run_success_test("file:///", "/");
run_success_test("file:///test", "/test");
run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt");
run_success_test(
"file:///dir/test%20test/test.txt",
"/dir/test test/test.txt",
);
fn run_success_test(specifier: &str, expected_path: &str) {
let result =
specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap())
.unwrap();
assert_eq!(result, PathBuf::from(expected_path));
}
}
#[test]
fn test_relative_specifier() {
let fixtures: Vec<(&str, &str, Option<&str>)> = vec![

View file

@ -22,6 +22,7 @@ use deno_core::PollEventLoopOptions;
use deno_core::SharedArrayBufferStore;
use deno_core::SourceMapGetter;
use deno_lockfile::Lockfile;
use deno_runtime::code_cache;
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
use deno_runtime::deno_fs;
use deno_runtime::deno_node;
@ -140,6 +141,7 @@ struct SharedWorkerState {
enable_future_features: bool,
disable_deprecated_api_warning: bool,
verbose_deprecated_api_warning: bool,
code_cache: Option<Arc<dyn code_cache::CodeCache>>,
}
impl SharedWorkerState {
@ -411,6 +413,7 @@ impl CliMainWorkerFactory {
enable_future_features: bool,
disable_deprecated_api_warning: bool,
verbose_deprecated_api_warning: bool,
code_cache: Option<Arc<dyn code_cache::CodeCache>>,
) -> Self {
Self {
shared: Arc::new(SharedWorkerState {
@ -434,6 +437,7 @@ impl CliMainWorkerFactory {
enable_future_features,
disable_deprecated_api_warning,
verbose_deprecated_api_warning,
code_cache,
}),
}
}
@ -628,6 +632,7 @@ impl CliMainWorkerFactory {
stdio,
feature_checker,
skip_op_registration: shared.options.skip_op_registration,
v8_code_cache: shared.code_cache.clone(),
};
let mut worker = MainWorker::bootstrap_from_options(

View file

@ -112,6 +112,7 @@ log.workspace = true
netif = "0.1.6"
notify.workspace = true
once_cell.workspace = true
percent-encoding.workspace = true
regex.workspace = true
ring.workspace = true
rustyline = { workspace = true, features = ["custom-bindings"] }

31
runtime/code_cache.rs Normal file
View file

@ -0,0 +1,31 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
pub enum CodeCacheType {
EsModule,
Script,
}
impl CodeCacheType {
pub fn as_str(&self) -> &str {
match self {
Self::EsModule => "esmodule",
Self::Script => "script",
}
}
}
pub trait CodeCache: Send + Sync {
fn get_sync(
&self,
specifier: &str,
code_cache_type: CodeCacheType,
source_hash: &str,
) -> Option<Vec<u8>>;
fn set_sync(
&self,
specifier: &str,
code_cache_type: CodeCacheType,
source_hash: &str,
data: &[u8],
);
}

View file

@ -1,6 +1,8 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_ast::ModuleSpecifier;
use deno_core::anyhow::Context;
use deno_core::error::uri_error;
use deno_core::error::AnyError;
pub use deno_core::normalize_path;
use std::path::Path;
@ -18,6 +20,60 @@ pub fn resolve_from_cwd(path: &Path) -> Result<PathBuf, AnyError> {
}
}
/// Attempts to convert a specifier to a file path. By default, uses the Url
/// crate's `to_file_path()` method, but falls back to try and resolve unix-style
/// paths on Windows.
pub fn specifier_to_file_path(
specifier: &ModuleSpecifier,
) -> Result<PathBuf, AnyError> {
let result = if specifier.scheme() != "file" {
Err(())
} else if cfg!(windows) {
match specifier.to_file_path() {
Ok(path) => Ok(path),
Err(()) => {
// This might be a unix-style path which is used in the tests even on Windows.
// Attempt to see if we can convert it to a `PathBuf`. This code should be removed
// once/if https://github.com/servo/rust-url/issues/730 is implemented.
if specifier.scheme() == "file"
&& specifier.host().is_none()
&& specifier.port().is_none()
&& specifier.path_segments().is_some()
{
let path_str = specifier.path();
match String::from_utf8(
percent_encoding::percent_decode(path_str.as_bytes()).collect(),
) {
Ok(path_str) => Ok(PathBuf::from(path_str)),
Err(_) => Err(()),
}
} else {
Err(())
}
}
}
} else {
specifier.to_file_path()
};
match result {
Ok(path) => Ok(path),
Err(()) => Err(uri_error(format!(
"Invalid file path.\n Specifier: {specifier}"
))),
}
}
pub fn code_timestamp(specifier: &str) -> Result<u64, AnyError> {
let specifier = ModuleSpecifier::parse(specifier)?;
let path = specifier_to_file_path(&specifier)?;
#[allow(clippy::disallowed_methods)]
let timestamp = std::fs::metadata(path)?
.modified()?
.duration_since(std::time::UNIX_EPOCH)?
.as_millis() as u64;
Ok(timestamp)
}
#[cfg(test)]
mod tests {
use super::*;
@ -69,4 +125,22 @@ mod tests {
let absolute_expected = cwd.join(expected);
assert_eq!(resolve_from_cwd(expected).unwrap(), absolute_expected);
}
#[test]
fn test_specifier_to_file_path() {
run_success_test("file:///", "/");
run_success_test("file:///test", "/test");
run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt");
run_success_test(
"file:///dir/test%20test/test.txt",
"/dir/test test/test.txt",
);
fn run_success_test(specifier: &str, expected_path: &str) {
let result =
specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap())
.unwrap();
assert_eq!(result, PathBuf::from(expected_path));
}
}
}

View file

@ -26,6 +26,7 @@ pub use deno_webidl;
pub use deno_websocket;
pub use deno_webstorage;
pub mod code_cache;
pub mod errors;
pub mod fmt_errors;
pub mod fs_util;

View file

@ -1,4 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::borrow::Cow;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::atomic::AtomicBool;
@ -41,6 +42,9 @@ use deno_tls::RootCertStoreProvider;
use deno_web::BlobStore;
use log::debug;
use crate::code_cache::CodeCache;
use crate::code_cache::CodeCacheType;
use crate::fs_util::code_timestamp;
use crate::inspector_server::InspectorServer;
use crate::ops;
use crate::permissions::PermissionsContainer;
@ -193,6 +197,9 @@ pub struct WorkerOptions {
pub compiled_wasm_module_store: Option<CompiledWasmModuleStore>,
pub stdio: Stdio,
pub feature_checker: Arc<FeatureChecker>,
/// V8 code cache for module and script source code.
pub v8_code_cache: Option<Arc<dyn CodeCache>>,
}
impl Default for WorkerOptions {
@ -227,6 +234,7 @@ impl Default for WorkerOptions {
bootstrap: Default::default(),
stdio: Default::default(),
feature_checker: Default::default(),
v8_code_cache: Default::default(),
}
}
}
@ -296,6 +304,51 @@ pub fn create_op_metrics(
(op_summary_metrics, op_metrics_factory_fn)
}
fn get_code_cache(
code_cache: Arc<dyn CodeCache>,
specifier: &str,
) -> Option<Vec<u8>> {
// Code hashes are not maintained for op_eval_context scripts. Instead we use
// the modified timestamp from the local file system.
if let Ok(code_timestamp) = code_timestamp(specifier) {
code_cache
.get_sync(
specifier,
CodeCacheType::Script,
code_timestamp.to_string().as_str(),
)
.inspect(|_| {
// This log line is also used by tests.
log::debug!(
"V8 code cache hit for script: {specifier}, [{code_timestamp}]"
);
})
} else {
None
}
}
fn set_code_cache(
code_cache: Arc<dyn CodeCache>,
specifier: &str,
data: &[u8],
) {
// Code hashes are not maintained for op_eval_context scripts. Instead we use
// the modified timestamp from the local file system.
if let Ok(code_timestamp) = code_timestamp(specifier) {
// This log line is also used by tests.
log::debug!(
"Updating V8 code cache for script: {specifier}, [{code_timestamp}]",
);
code_cache.set_sync(
specifier,
CodeCacheType::Script,
code_timestamp.to_string().as_str(),
data,
);
}
}
impl MainWorker {
pub fn bootstrap_from_options(
main_module: ModuleSpecifier,
@ -495,6 +548,18 @@ impl MainWorker {
validate_import_attributes_cb: Some(Box::new(
validate_import_attributes_callback,
)),
enable_code_cache: options.v8_code_cache.is_some(),
eval_context_code_cache_cbs: options.v8_code_cache.map(|cache| {
let cache_clone = cache.clone();
(
Box::new(move |specifier: &str| {
Ok(get_code_cache(cache.clone(), specifier).map(Cow::Owned))
}) as Box<dyn Fn(&_) -> _>,
Box::new(move |specifier: &str, data: &[u8]| {
set_code_cache(cache_clone.clone(), specifier, data);
}) as Box<dyn Fn(&_, &_)>,
)
}),
..Default::default()
});

View file

@ -5177,3 +5177,205 @@ fn run_etag_delete_source_cache() {
"[WILDCARD]Cache body not found. Trying again without etag.[WILDCARD]",
);
}
#[test]
fn code_cache_test() {
let deno_dir = TempDir::new();
let test_context = TestContextBuilder::new().use_temp_cwd().build();
let temp_dir = test_context.temp_dir();
temp_dir.write("main.js", "console.log('Hello World - A');");
// First run with no prior cache.
{
let output = test_context
.new_command()
.env("DENO_DIR", deno_dir.path())
.arg("run")
.arg("-Ldebug")
.arg("main.js")
.split_output()
.run();
output
.assert_stdout_matches_text("Hello World - A[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]");
assert!(!output.stderr().contains("V8 code cache hit"));
// Check that the code cache database exists.
let code_cache_path = deno_dir.path().join("v8_code_cache_v1");
assert!(code_cache_path.exists());
}
// 2nd run with cache.
{
let output = test_context
.new_command()
.env("DENO_DIR", deno_dir.path())
.arg("run")
.arg("-Ldebug")
.arg("main.js")
.split_output()
.run();
output
.assert_stdout_matches_text("Hello World - A[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/main.js[WILDCARD]");
assert!(!output.stderr().contains("Updating V8 code cache"));
}
// Rerun with --no-code-cache.
{
let output = test_context
.new_command()
.env("DENO_DIR", deno_dir.path())
.arg("run")
.arg("-Ldebug")
.arg("--no-code-cache")
.arg("main.js")
.split_output()
.run();
output
.assert_stdout_matches_text("Hello World - A[WILDCARD]")
.skip_stderr_check();
assert!(!output.stderr().contains("V8 code cache"));
}
// Modify the script, and make sure that the cache is rejected.
temp_dir.write("main.js", "console.log('Hello World - B');");
{
let output = test_context
.new_command()
.env("DENO_DIR", deno_dir.path())
.arg("run")
.arg("-Ldebug")
.arg("main.js")
.split_output()
.run();
output
.assert_stdout_matches_text("Hello World - B[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]");
assert!(!output.stderr().contains("V8 code cache hit"));
}
}
#[test]
fn code_cache_npm_test() {
let deno_dir = TempDir::new();
let test_context = TestContextBuilder::new()
.use_temp_cwd()
.use_http_server()
.build();
let temp_dir = test_context.temp_dir();
temp_dir.write(
"main.js",
"import chalk from \"npm:chalk@5\";console.log(chalk('Hello World'));",
);
// First run with no prior cache.
{
let output = test_context
.new_command()
.env("DENO_DIR", deno_dir.path())
.envs(env_vars_for_npm_tests())
.arg("run")
.arg("-Ldebug")
.arg("-A")
.arg("main.js")
.split_output()
.run();
output
.assert_stdout_matches_text("Hello World[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/npm/registry/chalk/5.[WILDCARD]/source/index.js[WILDCARD]");
assert!(!output.stderr().contains("V8 code cache hit"));
// Check that the code cache database exists.
let code_cache_path = deno_dir.path().join("v8_code_cache_v1");
assert!(code_cache_path.exists());
}
// 2nd run with cache.
{
let output = test_context
.new_command()
.env("DENO_DIR", deno_dir.path())
.envs(env_vars_for_npm_tests())
.arg("run")
.arg("-Ldebug")
.arg("-A")
.arg("main.js")
.split_output()
.run();
output
.assert_stdout_matches_text("Hello World[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/main.js[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/npm/registry/chalk/5.[WILDCARD]/source/index.js[WILDCARD]");
assert!(!output.stderr().contains("Updating V8 code cache"));
}
}
#[test]
fn code_cache_npm_with_require_test() {
let deno_dir = TempDir::new();
let test_context = TestContextBuilder::new()
.use_temp_cwd()
.use_http_server()
.build();
let temp_dir = test_context.temp_dir();
temp_dir.write(
"main.js",
"import fraction from \"npm:autoprefixer\";console.log(typeof fraction);",
);
// First run with no prior cache.
{
let output = test_context
.new_command()
.env("DENO_DIR", deno_dir.path())
.envs(env_vars_for_npm_tests())
.arg("run")
.arg("-Ldebug")
.arg("-A")
.arg("main.js")
.split_output()
.run();
output
.assert_stdout_matches_text("function[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for script: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for script: file:///[WILDCARD]/npm/registry/browserslist/[WILDCARD]/index.js[WILDCARD]");
assert!(!output.stderr().contains("V8 code cache hit"));
// Check that the code cache database exists.
let code_cache_path = deno_dir.path().join("v8_code_cache_v1");
assert!(code_cache_path.exists());
}
// 2nd run with cache.
{
let output = test_context
.new_command()
.env("DENO_DIR", deno_dir.path())
.envs(env_vars_for_npm_tests())
.arg("run")
.arg("-Ldebug")
.arg("-A")
.arg("main.js")
.split_output()
.run();
output
.assert_stdout_matches_text("function[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/main.js[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]V8 code cache hit for script: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]")
.assert_stderr_matches_text("[WILDCARD]V8 code cache hit for script: file:///[WILDCARD]/npm/registry/browserslist/[WILDCARD]/index.js[WILDCARD]");
assert!(!output.stderr().contains("Updating V8 code cache"));
}
}

View file

@ -14,4 +14,4 @@ setTimeout(async () => {
setTimeout(() => {
console.log("Success");
}, 50);
}, 200);