diff options
author | Igor Zinkovsky <igor@deno.com> | 2024-04-17 07:19:55 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-17 07:19:55 -0700 |
commit | b3d7df55357ea6fc6f5141b64a9638ddb39b0f63 (patch) | |
tree | 0ca14140c7e080ed3367a7352bbaf3ed5f7a0f48 /runtime | |
parent | 9acbf90b06bf79dd6e4cf2428b3566da009bed65 (diff) |
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>
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/Cargo.toml | 1 | ||||
-rw-r--r-- | runtime/code_cache.rs | 31 | ||||
-rw-r--r-- | runtime/fs_util.rs | 74 | ||||
-rw-r--r-- | runtime/lib.rs | 1 | ||||
-rw-r--r-- | runtime/worker.rs | 65 |
5 files changed, 172 insertions, 0 deletions
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 97a6d1397..8028a570d 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -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"] } diff --git a/runtime/code_cache.rs b/runtime/code_cache.rs new file mode 100644 index 000000000..ccc070365 --- /dev/null +++ b/runtime/code_cache.rs @@ -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], + ); +} diff --git a/runtime/fs_util.rs b/runtime/fs_util.rs index f7c006a91..09b107300 100644 --- a/runtime/fs_util.rs +++ b/runtime/fs_util.rs @@ -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)); + } + } } diff --git a/runtime/lib.rs b/runtime/lib.rs index 72fa1cef8..f33e9b7e3 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -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; diff --git a/runtime/worker.rs b/runtime/worker.rs index 956fa2d32..ee6b256ff 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -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() }); |