Skip to content

Commit

Permalink
Real paths are now cached
Browse files Browse the repository at this point in the history
  • Loading branch information
james-pre committed Nov 12, 2024
1 parent d5431c0 commit e7db92d
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnSave": true,
"cSpell.words": ["mknod"]
"cSpell.words": ["canonicalized", "mknod"]
}
10 changes: 10 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ export interface Configuration<T extends ConfigMounts> 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.
*
Expand Down Expand Up @@ -182,6 +191,7 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });

cache.stats.isEnabled = configuration.cacheStats ?? false;
cache.paths.isEnabled = configuration.cachePaths ?? false;
config.checkAccess = !configuration.disableAccessChecks;
config.updateOnRead = !configuration.disableUpdateOnRead;
config.syncImmediately = !configuration.onlySyncOnClose;
Expand Down
20 changes: 17 additions & 3 deletions src/emulation/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import type { Stats } from '../stats.js';

/**
* Used for caching data
* @internal
*/
export class Cache<T> {
public isEnabled: boolean = false;

protected sync = new Map<string, T>();

protected async = new Map<string, Promise<T | undefined>>();
protected async = new Map<string, Promise<T>>();

/**
* Gets data from the cache, if is exists and the cache is enabled.
Expand All @@ -25,6 +29,7 @@ export class Cache<T> {
if (!this.isEnabled) return;

this.sync.set(path, value);
this.async.set(path, Promise.resolve(value));
}

/**
Expand All @@ -39,7 +44,7 @@ export class Cache<T> {
/**
* Gets data from the cache, if it exists and the cache is enabled.
*/
get(path: string): Promise<T | undefined> | undefined {
get(path: string): Promise<T> | undefined {
if (!this.isEnabled) return;

return this.async.get(path);
Expand All @@ -48,10 +53,11 @@ export class Cache<T> {
/**
* Adds data if the cache is enabled
*/
set(path: string, value: Promise<T | undefined>): void {
set(path: string, value: Promise<T>): void {
if (!this.isEnabled) return;

this.async.set(path, value);
void value.then(v => this.sync.set(path, v));
}

/**
Expand All @@ -64,4 +70,12 @@ export class Cache<T> {
}
}

/**
* Used to cache
*/
export const stats = new Cache<Stats>();

/**
* Used to cache realpath lookups
*/
export const paths = new Cache<string>();
21 changes: 11 additions & 10 deletions src/emulation/promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | Buffer> {

Check warning on line 892 in src/emulation/promises.ts

View workflow job for this annotation

GitHub Actions / CI

'options' is defined but never used
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;
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 3 additions & 2 deletions src/emulation/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down

0 comments on commit e7db92d

Please sign in to comment.