Skip to content

Commit

Permalink
fs,module: add module-loader-only realpath cache
Browse files Browse the repository at this point in the history
Reintroduce a realpath cache with the same mechanisms which existed
before b488b19
(`fs: optimize realpath using uv_fs_realpath()`), but only for
the synchronous version and with the cache being passed as a
hidden option to make sure it is only used internally.

The cache is hidden from userland applications because it has been
decided that fully reintroducing as part of the public API might stand
in the way of future optimizations.

PR-URL: #8100
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
addaleax committed Sep 30, 2016
1 parent 7bc6aea commit c084287
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 23 deletions.
62 changes: 41 additions & 21 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,10 @@ function encodeRealpathResult(result, options, err) {
}
}

// This is removed from the fs exports in lib/module.js in order to make
// sure that this stays internal.
const realpathCacheKey = fs.realpathCacheKey = Symbol('realpathCacheKey');

fs.realpathSync = function realpathSync(p, options) {
if (!options)
options = {};
Expand All @@ -1528,6 +1532,13 @@ fs.realpathSync = function realpathSync(p, options) {

const seenLinks = {};
const knownHard = {};
const cache = options[realpathCacheKey];
const original = p;

const maybeCachedResult = cache && cache.get(p);
if (maybeCachedResult) {
return maybeCachedResult;
}

// current character position in p
var pos;
Expand Down Expand Up @@ -1568,39 +1579,47 @@ fs.realpathSync = function realpathSync(p, options) {
pos = nextPartRe.lastIndex;

// continue if not a symlink
if (knownHard[base]) {
if (knownHard[base] || (cache && cache.get(base) === base)) {
continue;
}

var resolvedLink;
var stat = fs.lstatSync(base);
if (!stat.isSymbolicLink()) {
knownHard[base] = true;
continue;
}
const maybeCachedResolved = cache && cache.get(base);
if (maybeCachedResolved) {
resolvedLink = maybeCachedResolved;
} else {
var stat = fs.lstatSync(base);
if (!stat.isSymbolicLink()) {
knownHard[base] = true;
continue;
}

// read the link if it wasn't read before
// dev/ino always return 0 on windows, so skip the check.
var linkTarget = null;
if (!isWindows) {
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
if (seenLinks.hasOwnProperty(id)) {
linkTarget = seenLinks[id];
// read the link if it wasn't read before
// dev/ino always return 0 on windows, so skip the check.
let linkTarget = null;
let id;
if (!isWindows) {
id = `${stat.dev.toString(32)}:${stat.ino.toString(32)}`;
if (seenLinks.hasOwnProperty(id)) {
linkTarget = seenLinks[id];
}
}
}
if (linkTarget === null) {
fs.statSync(base);
linkTarget = fs.readlinkSync(base);
}
resolvedLink = pathModule.resolve(previous, linkTarget);
if (linkTarget === null) {
fs.statSync(base);
linkTarget = fs.readlinkSync(base);
}
resolvedLink = pathModule.resolve(previous, linkTarget);

if (!isWindows) seenLinks[id] = linkTarget;
if (cache) cache.set(base, resolvedLink);
if (!isWindows) seenLinks[id] = linkTarget;
}

// resolve the link, then start over
p = pathModule.resolve(resolvedLink, p.slice(pos));
start();
}

if (cache) cache.set(original, p);
return encodeRealpathResult(p, options);
};

Expand Down Expand Up @@ -1696,8 +1715,9 @@ fs.realpath = function realpath(p, options, callback) {
// stat & read the link if not read before
// call gotTarget as soon as the link target is known
// dev/ino always return 0 on windows, so skip the check.
let id;
if (!isWindows) {
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
id = `${stat.dev.toString(32)}:${stat.ino.toString(32)}`;
if (seenLinks.hasOwnProperty(id)) {
return gotTarget(null, seenLinks[id], base);
}
Expand Down
18 changes: 16 additions & 2 deletions lib/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ function tryPackage(requestPath, exts, isMain) {
tryExtensions(path.resolve(filename, 'index'), exts, isMain);
}

// In order to minimize unnecessary lstat() calls,
// this cache is a list of known-real paths.
// Set to an empty Map to reset.
const realpathCache = new Map();

const realpathCacheKey = fs.realpathCacheKey;
delete fs.realpathCacheKey;

// check if the file exists and is not a directory
// if using --preserve-symlinks and isMain is false,
// keep symlinks intact, otherwise resolve to the
Expand All @@ -118,7 +126,13 @@ function tryFile(requestPath, isMain) {
if (preserveSymlinks && !isMain) {
return rc === 0 && path.resolve(requestPath);
}
return rc === 0 && fs.realpathSync(requestPath);
return rc === 0 && toRealPath(requestPath);
}

function toRealPath(requestPath) {
return fs.realpathSync(requestPath, {
[realpathCacheKey]: realpathCache
});
}

// given a path check a the file exists with any of the set extensions
Expand Down Expand Up @@ -164,7 +178,7 @@ Module._findPath = function(request, paths, isMain) {
if (preserveSymlinks && !isMain) {
filename = path.resolve(basePath);
} else {
filename = fs.realpathSync(basePath);
filename = toRealPath(basePath);
}
} else if (rc === 1) { // Directory.
if (exts === undefined)
Expand Down

0 comments on commit c084287

Please sign in to comment.