diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2024-07-17 05:35:51 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-17 18:05:51 +0530 |
commit | 078def0ff8501bb07f3f286515acd8c6a2181037 (patch) | |
tree | 6811ec33761ede11e5213393e7ea3732fd54f292 | |
parent | 568dd132fb0a47f9afb11bffec341c7481dda75c (diff) |
perf(ext/node): optimize fs.exists[Sync] (#24613)
Use `access` on *nix and `GetFileAttributesW` on Windows.
[Benchmark](https://paste.divy.work/p/-gq8Ark.js):
```
$ deno run -A bench.mjs # main (568dd)
existsSync: 8980.636629ms
$ target/release/deno run -A bench.mjs # this PR
existsSync: 6448.7604519999995ms
$ bun bench.mjs
existsSync: 6562.88671ms
$ node bench.mjs
existsSync: 7740.064653ms
```
Ref https://github.com/denoland/deno/pull/24434#discussion_r1679777912
-rw-r--r-- | ext/fs/interface.rs | 3 | ||||
-rw-r--r-- | ext/fs/std_fs.rs | 35 | ||||
-rw-r--r-- | ext/node/lib.rs | 1 | ||||
-rw-r--r-- | ext/node/ops/fs.rs | 23 | ||||
-rw-r--r-- | ext/node/polyfills/_fs/_fs_exists.ts | 4 |
5 files changed, 63 insertions, 3 deletions
diff --git a/ext/fs/interface.rs b/ext/fs/interface.rs index 2fdfa2186..09e16aff1 100644 --- a/ext/fs/interface.rs +++ b/ext/fs/interface.rs @@ -315,6 +315,9 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync { fn exists_sync(&self, path: &Path) -> bool { self.stat_sync(path).is_ok() } + async fn exists_async(&self, path: PathBuf) -> FsResult<bool> { + Ok(self.stat_async(path).await.is_ok()) + } fn read_text_file_lossy_sync( &self, diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs index 7903700c3..7fc33a8ad 100644 --- a/ext/fs/std_fs.rs +++ b/ext/fs/std_fs.rs @@ -173,6 +173,15 @@ impl FileSystem for RealFs { spawn_blocking(move || lstat(&path)).await?.map(Into::into) } + fn exists_sync(&self, path: &Path) -> bool { + exists(path) + } + async fn exists_async(&self, path: PathBuf) -> FsResult<bool> { + spawn_blocking(move || exists(&path)) + .await + .map_err(Into::into) + } + fn realpath_sync(&self, path: &Path) -> FsResult<PathBuf> { realpath(path) } @@ -837,6 +846,32 @@ fn stat_extra( } } +fn exists(path: &Path) -> bool { + #[cfg(unix)] + { + use nix::unistd::access; + use nix::unistd::AccessFlags; + access(path, AccessFlags::F_OK).is_ok() + } + + #[cfg(windows)] + { + use std::os::windows::ffi::OsStrExt; + use winapi::um::fileapi::GetFileAttributesW; + use winapi::um::fileapi::INVALID_FILE_ATTRIBUTES; + + let path = path + .as_os_str() + .encode_wide() + .chain(std::iter::once(0)) + .collect::<Vec<_>>(); + // Safety: `path` is a null-terminated string + let attrs = unsafe { GetFileAttributesW(path.as_ptr()) }; + + attrs != INVALID_FILE_ATTRIBUTES + } +} + fn realpath(path: &Path) -> FsResult<PathBuf> { Ok(deno_core::strip_unc_prefix(path.canonicalize()?)) } diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 13f9abc60..5be0fffa1 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -313,6 +313,7 @@ deno_core::extension!(deno_node, ops::crypto::x509::op_node_x509_get_serial_number, ops::crypto::x509::op_node_x509_key_usage, ops::fs::op_node_fs_exists_sync<P>, + ops::fs::op_node_fs_exists<P>, ops::fs::op_node_cp_sync<P>, ops::fs::op_node_cp<P>, ops::fs::op_node_lchown_sync<P>, diff --git a/ext/node/ops/fs.rs b/ext/node/ops/fs.rs index 47b66ee1d..687903325 100644 --- a/ext/node/ops/fs.rs +++ b/ext/node/ops/fs.rs @@ -26,7 +26,28 @@ where .borrow_mut::<P>() .check_read_with_api_name(&path, Some("node:fs.existsSync()"))?; let fs = state.borrow::<FileSystemRc>(); - Ok(fs.lstat_sync(&path).is_ok()) + Ok(fs.exists_sync(&path)) +} + +#[op2(async)] +pub async fn op_node_fs_exists<P>( + state: Rc<RefCell<OpState>>, + #[string] path: String, +) -> Result<bool, AnyError> +where + P: NodePermissions + 'static, +{ + let path = PathBuf::from(path); + + let fs = { + let mut state = state.borrow_mut(); + state + .borrow_mut::<P>() + .check_read_with_api_name(&path, Some("node:fs.exists()"))?; + state.borrow::<FileSystemRc>().clone() + }; + + Ok(fs.exists_async(path).await?) } #[op2(fast)] diff --git a/ext/node/polyfills/_fs/_fs_exists.ts b/ext/node/polyfills/_fs/_fs_exists.ts index 57df1f07c..b5bbe235a 100644 --- a/ext/node/polyfills/_fs/_fs_exists.ts +++ b/ext/node/polyfills/_fs/_fs_exists.ts @@ -3,7 +3,7 @@ // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials -import { op_node_fs_exists_sync } from "ext:core/ops"; +import { op_node_fs_exists, op_node_fs_exists_sync } from "ext:core/ops"; import { pathFromURL } from "ext:deno_web/00_infra.js"; @@ -16,7 +16,7 @@ type ExistsCallback = (exists: boolean) => void; */ export function exists(path: string | URL, callback: ExistsCallback) { path = path instanceof URL ? pathFromURL(path) : path; - Deno.lstat(path).then(() => callback(true), () => callback(false)); + op_node_fs_exists(path).then(callback); } // The callback of fs.exists doesn't have standard callback signature. |