1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 07:14:47 -05:00

perf(node): cache realpath_sync calls in read permission check (#19379)

This commit is contained in:
Gustrb 2023-06-09 15:41:18 -03:00 committed by GitHub
parent ff690b0ab4
commit 2b2eebd583
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 45 deletions

View file

@ -1,9 +1,11 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::collections::HashMap;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
use async_trait::async_trait; use async_trait::async_trait;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
@ -54,6 +56,69 @@ pub trait NpmPackageFsResolver: Send + Sync {
) -> Result<(), AnyError>; ) -> Result<(), AnyError>;
} }
#[derive(Debug)]
pub struct RegistryReadPermissionChecker {
fs: Arc<dyn FileSystem>,
cache: Mutex<HashMap<PathBuf, PathBuf>>,
registry_path: PathBuf,
}
impl RegistryReadPermissionChecker {
pub fn new(fs: Arc<dyn FileSystem>, registry_path: PathBuf) -> Self {
Self {
fs,
registry_path,
cache: Default::default(),
}
}
pub fn ensure_registry_read_permission(
&self,
permissions: &dyn NodePermissions,
path: &Path,
) -> Result<(), AnyError> {
// allow reading if it's in the node_modules
let is_path_in_node_modules = path.starts_with(&self.registry_path)
&& path
.components()
.all(|c| !matches!(c, std::path::Component::ParentDir));
if is_path_in_node_modules {
let mut cache = self.cache.lock().unwrap();
let registry_path_canon = match cache.get(&self.registry_path) {
Some(canon) => canon.clone(),
None => {
let canon = self.fs.realpath_sync(&self.registry_path)?;
cache.insert(self.registry_path.to_path_buf(), canon.clone());
canon
}
};
let path_canon = match cache.get(path) {
Some(canon) => canon.clone(),
None => {
let canon = self.fs.realpath_sync(path);
if let Err(e) = &canon {
if e.kind() == ErrorKind::NotFound {
return Ok(());
}
}
let canon = canon?;
cache.insert(path.to_path_buf(), canon.clone());
canon
}
};
if path_canon.starts_with(registry_path_canon) {
return Ok(());
}
}
permissions.check_read(path)
}
}
/// Caches all the packages in parallel. /// Caches all the packages in parallel.
pub async fn cache_packages( pub async fn cache_packages(
mut packages: Vec<NpmResolutionPackage>, mut packages: Vec<NpmResolutionPackage>,
@ -90,35 +155,6 @@ pub async fn cache_packages(
Ok(()) Ok(())
} }
pub fn ensure_registry_read_permission(
fs: &Arc<dyn FileSystem>,
permissions: &dyn NodePermissions,
registry_path: &Path,
path: &Path,
) -> Result<(), AnyError> {
// allow reading if it's in the node_modules
if path.starts_with(registry_path)
&& path
.components()
.all(|c| !matches!(c, std::path::Component::ParentDir))
{
// todo(dsherret): cache this?
if let Ok(registry_path) = fs.realpath_sync(registry_path) {
match fs.realpath_sync(path) {
Ok(path) if path.starts_with(registry_path) => {
return Ok(());
}
Err(e) if e.kind() == ErrorKind::NotFound => {
return Ok(());
}
_ => {} // ignore
}
}
}
permissions.check_read(path)
}
/// Gets the corresponding @types package for the provided package name. /// Gets the corresponding @types package for the provided package name.
pub fn types_package_name(package_name: &str) -> String { pub fn types_package_name(package_name: &str) -> String {
debug_assert!(!package_name.starts_with("@types/")); debug_assert!(!package_name.starts_with("@types/"));

View file

@ -23,18 +23,18 @@ use crate::npm::resolution::NpmResolution;
use crate::npm::resolvers::common::cache_packages; use crate::npm::resolvers::common::cache_packages;
use crate::npm::NpmCache; use crate::npm::NpmCache;
use super::common::ensure_registry_read_permission;
use super::common::types_package_name; use super::common::types_package_name;
use super::common::NpmPackageFsResolver; use super::common::NpmPackageFsResolver;
use super::common::RegistryReadPermissionChecker;
/// Resolves packages from the global npm cache. /// Resolves packages from the global npm cache.
#[derive(Debug)] #[derive(Debug)]
pub struct GlobalNpmPackageResolver { pub struct GlobalNpmPackageResolver {
fs: Arc<dyn FileSystem>,
cache: Arc<NpmCache>, cache: Arc<NpmCache>,
resolution: Arc<NpmResolution>, resolution: Arc<NpmResolution>,
registry_url: Url, registry_url: Url,
system_info: NpmSystemInfo, system_info: NpmSystemInfo,
registry_read_permission_checker: RegistryReadPermissionChecker,
} }
impl GlobalNpmPackageResolver { impl GlobalNpmPackageResolver {
@ -46,11 +46,14 @@ impl GlobalNpmPackageResolver {
system_info: NpmSystemInfo, system_info: NpmSystemInfo,
) -> Self { ) -> Self {
Self { Self {
fs, cache: cache.clone(),
cache,
resolution, resolution,
registry_url, registry_url: registry_url.clone(),
system_info, system_info,
registry_read_permission_checker: RegistryReadPermissionChecker::new(
fs,
cache.registry_folder(&registry_url),
),
} }
} }
@ -156,7 +159,8 @@ impl NpmPackageFsResolver for GlobalNpmPackageResolver {
permissions: &dyn NodePermissions, permissions: &dyn NodePermissions,
path: &Path, path: &Path,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let registry_path = self.cache.registry_folder(&self.registry_url); self
ensure_registry_read_permission(&self.fs, permissions, &registry_path, path) .registry_read_permission_checker
.ensure_registry_read_permission(permissions, path)
} }
} }

View file

@ -41,9 +41,9 @@ use crate::npm::NpmCache;
use crate::util::fs::copy_dir_recursive; use crate::util::fs::copy_dir_recursive;
use crate::util::fs::hard_link_dir_recursive; use crate::util::fs::hard_link_dir_recursive;
use super::common::ensure_registry_read_permission;
use super::common::types_package_name; use super::common::types_package_name;
use super::common::NpmPackageFsResolver; use super::common::NpmPackageFsResolver;
use super::common::RegistryReadPermissionChecker;
/// Resolver that creates a local node_modules directory /// Resolver that creates a local node_modules directory
/// and resolves packages from it. /// and resolves packages from it.
@ -57,6 +57,7 @@ pub struct LocalNpmPackageResolver {
root_node_modules_path: PathBuf, root_node_modules_path: PathBuf,
root_node_modules_url: Url, root_node_modules_url: Url,
system_info: NpmSystemInfo, system_info: NpmSystemInfo,
registry_read_permission_checker: RegistryReadPermissionChecker,
} }
impl LocalNpmPackageResolver { impl LocalNpmPackageResolver {
@ -70,15 +71,19 @@ impl LocalNpmPackageResolver {
system_info: NpmSystemInfo, system_info: NpmSystemInfo,
) -> Self { ) -> Self {
Self { Self {
fs, fs: fs.clone(),
cache, cache,
progress_bar, progress_bar,
resolution, resolution,
registry_url, registry_url,
root_node_modules_url: Url::from_directory_path(&node_modules_folder) root_node_modules_url: Url::from_directory_path(&node_modules_folder)
.unwrap(), .unwrap(),
root_node_modules_path: node_modules_folder, root_node_modules_path: node_modules_folder.clone(),
system_info, system_info,
registry_read_permission_checker: RegistryReadPermissionChecker::new(
fs,
node_modules_folder,
),
} }
} }
@ -227,12 +232,9 @@ impl NpmPackageFsResolver for LocalNpmPackageResolver {
permissions: &dyn NodePermissions, permissions: &dyn NodePermissions,
path: &Path, path: &Path,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
ensure_registry_read_permission( self
&self.fs, .registry_read_permission_checker
permissions, .ensure_registry_read_permission(permissions, path)
&self.root_node_modules_path,
path,
)
} }
} }