From e7db92dd7e2671802bfdbd03d5efaeb1d3c9d36a Mon Sep 17 00:00:00 2001 From: James Prevett Date: Mon, 11 Nov 2024 19:31:52 -0600 Subject: [PATCH] Real paths are now cached --- .vscode/settings.json | 2 +- src/config.ts | 10 ++++++++++ src/emulation/cache.ts | 20 +++++++++++++++++--- src/emulation/promises.ts | 21 +++++++++++---------- src/emulation/sync.ts | 5 +++-- 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fe267a19..7dbe0f8a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,5 +18,5 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "editor.formatOnSave": true, - "cSpell.words": ["mknod"] + "cSpell.words": ["canonicalized", "mknod"] } diff --git a/src/config.ts b/src/config.ts index f96f33e0..dac96c27 100644 --- a/src/config.ts +++ b/src/config.ts @@ -108,6 +108,15 @@ export interface Configuration extends SharedConfig { */ cacheStats: boolean; + /** + * If true, enables caching realpath output + * + * This can increase performance. + * @experimental + * @default false + */ + cachePaths: boolean; + /** * If true, disables *all* permissions checking. * @@ -182,6 +191,7 @@ export async function configure(configuration: Partial { public isEnabled: boolean = false; protected sync = new Map(); - protected async = new Map>(); + protected async = new Map>(); /** * Gets data from the cache, if is exists and the cache is enabled. @@ -25,6 +29,7 @@ export class Cache { if (!this.isEnabled) return; this.sync.set(path, value); + this.async.set(path, Promise.resolve(value)); } /** @@ -39,7 +44,7 @@ export class Cache { /** * Gets data from the cache, if it exists and the cache is enabled. */ - get(path: string): Promise | undefined { + get(path: string): Promise | undefined { if (!this.isEnabled) return; return this.async.get(path); @@ -48,10 +53,11 @@ export class Cache { /** * Adds data if the cache is enabled */ - set(path: string, value: Promise): void { + set(path: string, value: Promise): void { if (!this.isEnabled) return; this.async.set(path, value); + void value.then(v => this.sync.set(path, v)); } /** @@ -64,4 +70,12 @@ export class Cache { } } +/** + * Used to cache + */ export const stats = new Cache(); + +/** + * Used to cache realpath lookups + */ +export const paths = new Cache(); diff --git a/src/emulation/promises.ts b/src/emulation/promises.ts index b05620de..ce11d5eb 100644 --- a/src/emulation/promises.ts +++ b/src/emulation/promises.ts @@ -892,16 +892,19 @@ export async function realpath(path: fs.PathLike, options?: fs.EncodingOption | export async function realpath(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding | fs.BufferEncodingOption): Promise { path = normalizePath(path); const { base, dir } = parse(path); - const lpath = join(dir == '/' ? '/' : await realpath(dir), base); + const lpath = join(dir == '/' ? '/' : await (cache.paths.get(dir) || realpath(dir)), base); const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath); try { - const stats = await fs.stat(resolvedPath); - if (!stats.isSymbolicLink()) { + const _stats = cache.stats.get(lpath) || fs.stat(resolvedPath); + cache.stats.set(lpath, _stats); + if (!(await _stats).isSymbolicLink()) { return lpath; } - return await realpath(mountPoint + (await readlink(lpath))); + const target = mountPoint + (await readlink(lpath)); + + return await (cache.paths.get(target) || realpath(target)); } catch (e) { if ((e as ErrnoError).code == 'ENOENT') { return path; @@ -965,20 +968,18 @@ access satisfies typeof promises.access; export async function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOptions) { path = normalizePath(path); - const _stats = - cache.stats.get(path) || + const stats = await (cache.stats.get(path) || stat(path).catch((error: ErrnoError) => { if (error.code == 'ENOENT' && options?.force) return undefined; throw error; - }); - - cache.stats.set(path, _stats); - const stats = await _stats; + })); if (!stats) { return; } + cache.stats.setSync(path, stats); + switch (stats.mode & constants.S_IFMT) { case constants.S_IFDIR: if (options?.recursive) { diff --git a/src/emulation/sync.ts b/src/emulation/sync.ts index bbdbde75..ac2ddcec 100644 --- a/src/emulation/sync.ts +++ b/src/emulation/sync.ts @@ -622,7 +622,7 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption): st export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs.BufferEncodingOption): string | Buffer { path = normalizePath(path); const { base, dir } = parse(path); - const lpath = join(dir == '/' ? '/' : realpathSync(dir), base); + const lpath = join(dir == '/' ? '/' : cache.paths.getSync(dir) || realpathSync(dir), base); const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath); try { @@ -631,7 +631,8 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs return lpath; } - return realpathSync(mountPoint + readlinkSync(lpath, options).toString()); + const target = mountPoint + readlinkSync(lpath, options).toString(); + return cache.paths.getSync(target) || realpathSync(target); } catch (e) { if ((e as ErrnoError).code == 'ENOENT') { return path;