Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs,module: add module-loader-only realpath cache #8100

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 41 additions & 21 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be exported from internal/fs? or is this meant to be public api?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no internal/fs because using that would break old graceful-fs versions… that’s what #6413 is all about.

It’s not meant to be public, and as the comment above indicates (if you have suggestions on how to clarify, that would be welcomed!), realpathCacheKey is removed from the exports of fs automatically. It’s a bit hacky, yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, fair enough. That would work I think, yeah.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that as soon as possible we should move this into a internal/fs once we're able to actually make that change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make sense but I wouldn’t consider that to be something urgent… by then we might have a working implementation in libuv.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having trouble keeping up with the can and cannot situation of internal/fs, but it appears to be there in master right now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I’d follow up with a commit moving this to internal/fs. That definitely can’t land in v6.x, though.


fs.realpathSync = function realpathSync(p, options) {
if (!options)
options = {};
Expand All @@ -1574,6 +1578,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 @@ -1614,39 +1625,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 @@ -1742,8 +1761,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