From c084287a608efcd339d085ae1c48457b31e9920a Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 14 Aug 2016 18:41:28 +0200 Subject: [PATCH] fs,module: add module-loader-only realpath cache Reintroduce a realpath cache with the same mechanisms which existed before b488b19eaf2b2e7a3ca5eccd2445e245847a5f76 (`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: https://github.com/nodejs/node/pull/8100 Reviewed-By: Bartosz Sosnowski Reviewed-By: James M Snell --- lib/fs.js | 62 ++++++++++++++++++++++++++++++++++----------------- lib/module.js | 18 +++++++++++++-- 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index a9e96bd736..17860ec05a 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -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 = {}; @@ -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; @@ -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); }; @@ -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); } diff --git a/lib/module.js b/lib/module.js index 96f1cfc42e..c61a27a9e3 100644 --- a/lib/module.js +++ b/lib/module.js @@ -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 @@ -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 @@ -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)