Skip to content

Commit c084287

Browse files
committed
fs,module: add module-loader-only realpath cache
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 <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 7bc6aea commit c084287

File tree

2 files changed

+57
-23
lines changed

2 files changed

+57
-23
lines changed

lib/fs.js

+41-21
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,10 @@ function encodeRealpathResult(result, options, err) {
15141514
}
15151515
}
15161516

1517+
// This is removed from the fs exports in lib/module.js in order to make
1518+
// sure that this stays internal.
1519+
const realpathCacheKey = fs.realpathCacheKey = Symbol('realpathCacheKey');
1520+
15171521
fs.realpathSync = function realpathSync(p, options) {
15181522
if (!options)
15191523
options = {};
@@ -1528,6 +1532,13 @@ fs.realpathSync = function realpathSync(p, options) {
15281532

15291533
const seenLinks = {};
15301534
const knownHard = {};
1535+
const cache = options[realpathCacheKey];
1536+
const original = p;
1537+
1538+
const maybeCachedResult = cache && cache.get(p);
1539+
if (maybeCachedResult) {
1540+
return maybeCachedResult;
1541+
}
15311542

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

15701581
// continue if not a symlink
1571-
if (knownHard[base]) {
1582+
if (knownHard[base] || (cache && cache.get(base) === base)) {
15721583
continue;
15731584
}
15741585

15751586
var resolvedLink;
1576-
var stat = fs.lstatSync(base);
1577-
if (!stat.isSymbolicLink()) {
1578-
knownHard[base] = true;
1579-
continue;
1580-
}
1587+
const maybeCachedResolved = cache && cache.get(base);
1588+
if (maybeCachedResolved) {
1589+
resolvedLink = maybeCachedResolved;
1590+
} else {
1591+
var stat = fs.lstatSync(base);
1592+
if (!stat.isSymbolicLink()) {
1593+
knownHard[base] = true;
1594+
continue;
1595+
}
15811596

1582-
// read the link if it wasn't read before
1583-
// dev/ino always return 0 on windows, so skip the check.
1584-
var linkTarget = null;
1585-
if (!isWindows) {
1586-
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
1587-
if (seenLinks.hasOwnProperty(id)) {
1588-
linkTarget = seenLinks[id];
1597+
// read the link if it wasn't read before
1598+
// dev/ino always return 0 on windows, so skip the check.
1599+
let linkTarget = null;
1600+
let id;
1601+
if (!isWindows) {
1602+
id = `${stat.dev.toString(32)}:${stat.ino.toString(32)}`;
1603+
if (seenLinks.hasOwnProperty(id)) {
1604+
linkTarget = seenLinks[id];
1605+
}
15891606
}
1590-
}
1591-
if (linkTarget === null) {
1592-
fs.statSync(base);
1593-
linkTarget = fs.readlinkSync(base);
1594-
}
1595-
resolvedLink = pathModule.resolve(previous, linkTarget);
1607+
if (linkTarget === null) {
1608+
fs.statSync(base);
1609+
linkTarget = fs.readlinkSync(base);
1610+
}
1611+
resolvedLink = pathModule.resolve(previous, linkTarget);
15961612

1597-
if (!isWindows) seenLinks[id] = linkTarget;
1613+
if (cache) cache.set(base, resolvedLink);
1614+
if (!isWindows) seenLinks[id] = linkTarget;
1615+
}
15981616

15991617
// resolve the link, then start over
16001618
p = pathModule.resolve(resolvedLink, p.slice(pos));
16011619
start();
16021620
}
16031621

1622+
if (cache) cache.set(original, p);
16041623
return encodeRealpathResult(p, options);
16051624
};
16061625

@@ -1696,8 +1715,9 @@ fs.realpath = function realpath(p, options, callback) {
16961715
// stat & read the link if not read before
16971716
// call gotTarget as soon as the link target is known
16981717
// dev/ino always return 0 on windows, so skip the check.
1718+
let id;
16991719
if (!isWindows) {
1700-
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
1720+
id = `${stat.dev.toString(32)}:${stat.ino.toString(32)}`;
17011721
if (seenLinks.hasOwnProperty(id)) {
17021722
return gotTarget(null, seenLinks[id], base);
17031723
}

lib/module.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ function tryPackage(requestPath, exts, isMain) {
109109
tryExtensions(path.resolve(filename, 'index'), exts, isMain);
110110
}
111111

112+
// In order to minimize unnecessary lstat() calls,
113+
// this cache is a list of known-real paths.
114+
// Set to an empty Map to reset.
115+
const realpathCache = new Map();
116+
117+
const realpathCacheKey = fs.realpathCacheKey;
118+
delete fs.realpathCacheKey;
119+
112120
// check if the file exists and is not a directory
113121
// if using --preserve-symlinks and isMain is false,
114122
// keep symlinks intact, otherwise resolve to the
@@ -118,7 +126,13 @@ function tryFile(requestPath, isMain) {
118126
if (preserveSymlinks && !isMain) {
119127
return rc === 0 && path.resolve(requestPath);
120128
}
121-
return rc === 0 && fs.realpathSync(requestPath);
129+
return rc === 0 && toRealPath(requestPath);
130+
}
131+
132+
function toRealPath(requestPath) {
133+
return fs.realpathSync(requestPath, {
134+
[realpathCacheKey]: realpathCache
135+
});
122136
}
123137

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

0 commit comments

Comments
 (0)