Support caching for realpath, use in module load
This adds support for a cache object to be passed to the fs.realpath and fs.realpathSync functions. The Module loader keeps an object around which caches the resulting realpaths that it looks up in the process of loading modules. This means that (at least as a result of loading modules) the same files and folders are never lstat()ed more than once. To reset the cache, set require("module")._realpathCache to an empty object. To disable the caching behavior, set it to null.
This commit is contained in:
parent
9de5043b50
commit
9bed5dcb2c
78
lib/fs.js
78
lib/fs.js
@ -514,15 +514,27 @@ if (isWindows) {
|
|||||||
// windows version
|
// windows version
|
||||||
fs.realpathSync = function realpathSync(p) {
|
fs.realpathSync = function realpathSync(p) {
|
||||||
var p = path.resolve(p);
|
var p = path.resolve(p);
|
||||||
|
if (cache && Object.prototype.hasOwnProperty.call(cache, p)) {
|
||||||
|
return cache[p];
|
||||||
|
}
|
||||||
fs.statSync(p);
|
fs.statSync(p);
|
||||||
|
if (cache) cache[p] = p;
|
||||||
return p;
|
return p;
|
||||||
};
|
};
|
||||||
|
|
||||||
// windows version
|
// windows version
|
||||||
fs.realpath = function(p, cb) {
|
fs.realpath = function(p, cache, cb) {
|
||||||
|
if (typeof cb !== 'function') {
|
||||||
|
cb = cache;
|
||||||
|
cache = null;
|
||||||
|
}
|
||||||
var p = path.resolve(p);
|
var p = path.resolve(p);
|
||||||
|
if (cache && Object.prototype.hasOwnProperty.call(cache, p)) {
|
||||||
|
return cb(null, cache[p]);
|
||||||
|
}
|
||||||
fs.stat(p, function(err) {
|
fs.stat(p, function(err) {
|
||||||
if (err) cb(err);
|
if (err) cb(err);
|
||||||
|
if (cache) cache[p] = p;
|
||||||
cb(null, p);
|
cb(null, p);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -535,11 +547,16 @@ if (isWindows) {
|
|||||||
var nextPartRe = /(.*?)(?:[\/]+|$)/g;
|
var nextPartRe = /(.*?)(?:[\/]+|$)/g;
|
||||||
|
|
||||||
// posix version
|
// posix version
|
||||||
fs.realpathSync = function realpathSync(p) {
|
fs.realpathSync = function realpathSync(p, cache) {
|
||||||
// make p is absolute
|
// make p is absolute
|
||||||
p = path.resolve(p);
|
p = path.resolve(p);
|
||||||
|
|
||||||
var seenLinks = {},
|
if (cache && Object.prototype.hasOwnProperty.call(cache, p)) {
|
||||||
|
return cache[p];
|
||||||
|
}
|
||||||
|
|
||||||
|
var original = p,
|
||||||
|
seenLinks = {},
|
||||||
knownHard = {};
|
knownHard = {};
|
||||||
|
|
||||||
// current character position in p
|
// current character position in p
|
||||||
@ -564,12 +581,19 @@ if (isWindows) {
|
|||||||
pos = nextPartRe.lastIndex;
|
pos = nextPartRe.lastIndex;
|
||||||
|
|
||||||
// continue if not a symlink, or if root
|
// continue if not a symlink, or if root
|
||||||
if (!base || knownHard[base]) {
|
if (!base || knownHard[base] || (cache && cache[base] === base)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resolvedLink;
|
||||||
|
if (cache && Object.prototype.hasOwnProperty.call(cache, base)) {
|
||||||
|
// some known symbolic link. no need to stat again.
|
||||||
|
resolvedLink = cache[base];
|
||||||
|
} else {
|
||||||
var stat = fs.lstatSync(base);
|
var stat = fs.lstatSync(base);
|
||||||
if (!stat.isSymbolicLink()) {
|
if (!stat.isSymbolicLink()) {
|
||||||
knownHard[base] = true;
|
knownHard[base] = true;
|
||||||
|
if (cache) cache[base] = base;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,24 +602,40 @@ if (isWindows) {
|
|||||||
if (!seenLinks[id]) {
|
if (!seenLinks[id]) {
|
||||||
fs.statSync(base);
|
fs.statSync(base);
|
||||||
seenLinks[id] = fs.readlinkSync(base);
|
seenLinks[id] = fs.readlinkSync(base);
|
||||||
|
resolvedLink = path.resolve(previous, seenLinks[id]);
|
||||||
|
// track this, if given a cache.
|
||||||
|
if (cache) cache[base] = resolvedLink;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve the link, then start over
|
// resolve the link, then start over
|
||||||
p = path.resolve(previous, seenLinks[id], p.slice(pos));
|
p = path.resolve(resolvedLink, p.slice(pos));
|
||||||
pos = 0;
|
pos = 0;
|
||||||
previous = base = current = '';
|
previous = base = current = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cache) cache[original] = p;
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// posix version
|
// posix version
|
||||||
fs.realpath = function realpath(p, cb) {
|
fs.realpath = function realpath(p, cache, cb) {
|
||||||
|
if (typeof cb !== 'function') {
|
||||||
|
cb = cache;
|
||||||
|
cache = null;
|
||||||
|
}
|
||||||
|
|
||||||
// make p is absolute
|
// make p is absolute
|
||||||
p = path.resolve(p);
|
p = path.resolve(p);
|
||||||
|
|
||||||
var seenLinks = {},
|
if (cache && Object.prototype.hasOwnProperty.call(cache, p)) {
|
||||||
|
return cb(null, cache[p]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var original = p,
|
||||||
|
seenLinks = {},
|
||||||
knownHard = {};
|
knownHard = {};
|
||||||
|
|
||||||
// current character position in p
|
// current character position in p
|
||||||
@ -613,6 +653,7 @@ if (isWindows) {
|
|||||||
function LOOP() {
|
function LOOP() {
|
||||||
// stop if scanned past end of path
|
// stop if scanned past end of path
|
||||||
if (pos >= p.length) {
|
if (pos >= p.length) {
|
||||||
|
if (cache) cache[original] = p;
|
||||||
return cb(null, p);
|
return cb(null, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,11 +665,16 @@ if (isWindows) {
|
|||||||
base = previous + result[1];
|
base = previous + result[1];
|
||||||
pos = nextPartRe.lastIndex;
|
pos = nextPartRe.lastIndex;
|
||||||
|
|
||||||
// continue if known to be hard or if root
|
// continue if known to be hard or if root or in cache already.
|
||||||
if (!base || knownHard[base]) {
|
if (!base || knownHard[base] || (cache && cache[base] === base)) {
|
||||||
return process.nextTick(LOOP);
|
return process.nextTick(LOOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cache && Object.prototype.hasOwnProperty.call(cache, base)) {
|
||||||
|
// known symbolic link. no need to stat again.
|
||||||
|
return gotResolvedLink(cache[base]);
|
||||||
|
}
|
||||||
|
|
||||||
return fs.lstat(base, gotStat);
|
return fs.lstat(base, gotStat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,6 +684,7 @@ if (isWindows) {
|
|||||||
// if not a symlink, skip to the next path part
|
// if not a symlink, skip to the next path part
|
||||||
if (!stat.isSymbolicLink()) {
|
if (!stat.isSymbolicLink()) {
|
||||||
knownHard[base] = true;
|
knownHard[base] = true;
|
||||||
|
if (cache) cache[base] = base;
|
||||||
return process.nextTick(LOOP);
|
return process.nextTick(LOOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,7 +692,7 @@ if (isWindows) {
|
|||||||
// call gotTarget as soon as the link target is known
|
// call gotTarget as soon as the link target is known
|
||||||
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
|
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
|
||||||
if (seenLinks[id]) {
|
if (seenLinks[id]) {
|
||||||
return gotTarget(null, seenLinks[id]);
|
return gotTarget(null, seenLinks[id], base);
|
||||||
}
|
}
|
||||||
fs.stat(base, function(err) {
|
fs.stat(base, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
@ -656,11 +703,18 @@ if (isWindows) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function gotTarget(err, target) {
|
function gotTarget(err, target, base) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
var resolvedLink = path.resolve(previous, target);
|
||||||
|
if (cache) cache[base] = resolvedLink;
|
||||||
|
gotResolvedLink(resolvedLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotResolvedLink(resolvedLink) {
|
||||||
|
|
||||||
// resolve the link, then start over
|
// resolve the link, then start over
|
||||||
p = path.resolve(previous, target, p.slice(pos));
|
p = path.resolve(resolvedLink, p.slice(pos));
|
||||||
pos = 0;
|
pos = 0;
|
||||||
previous = base = current = '';
|
previous = base = current = '';
|
||||||
|
|
||||||
|
@ -88,12 +88,17 @@ function tryPackage(requestPath, exts) {
|
|||||||
return tryFile(filename) || tryExtensions(filename, exts);
|
return tryFile(filename) || tryExtensions(filename, exts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In order to minimize unnecessary lstat() calls,
|
||||||
|
// this cache is a list of known-real paths.
|
||||||
|
// Set to an empty object to reset.
|
||||||
|
Module._realpathCache = {}
|
||||||
|
|
||||||
// check if the file exists and is not a directory
|
// check if the file exists and is not a directory
|
||||||
function tryFile(requestPath) {
|
function tryFile(requestPath) {
|
||||||
var fs = NativeModule.require('fs');
|
var fs = NativeModule.require('fs');
|
||||||
var stats = statPath(requestPath);
|
var stats = statPath(requestPath);
|
||||||
if (stats && !stats.isDirectory()) {
|
if (stats && !stats.isDirectory()) {
|
||||||
return fs.realpathSync(requestPath);
|
return fs.realpathSync(requestPath, Module._realpathCache);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -363,6 +363,31 @@ function test_abs_with_kids(cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_lying_cache_liar(cb) {
|
||||||
|
// this should not require *any* stat calls, since everything
|
||||||
|
// checked by realpath will be found in the cache.
|
||||||
|
console.log('test_lying_cache_liar');
|
||||||
|
var cache = { '/foo/bar/baz/bluff' : '/foo/bar/bluff',
|
||||||
|
'/1/2/3/4/5/6/7' : '/1',
|
||||||
|
'/a' : '/a',
|
||||||
|
'/a/b' : '/a/b',
|
||||||
|
'/a/b/c' : '/a/b',
|
||||||
|
'/a/b/d' : '/a/b/d' };
|
||||||
|
var rps = fs.realpathSync('/foo/bar/baz/bluff', cache);
|
||||||
|
assert.equal(cache['/foo/bar/baz/bluff'], rps);
|
||||||
|
fs.realpath('/1/2/3/4/5/6/7', cache, function(er, rp) {
|
||||||
|
assert.equal(cache['/1/2/3/4/5/6/7'], rp);
|
||||||
|
});
|
||||||
|
|
||||||
|
var test = '/a/b/c/d',
|
||||||
|
expect = '/a/b/d';
|
||||||
|
var actual = fs.realpathSync(test, cache);
|
||||||
|
assert.equal(expect, actual);
|
||||||
|
fs.realpath(test, cache, function(er, actual) {
|
||||||
|
assert.equal(expect, actual);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
var tests = [
|
var tests = [
|
||||||
@ -376,7 +401,8 @@ var tests = [
|
|||||||
test_deep_symlink_mix,
|
test_deep_symlink_mix,
|
||||||
test_non_symlinks,
|
test_non_symlinks,
|
||||||
test_escape_cwd,
|
test_escape_cwd,
|
||||||
test_abs_with_kids
|
test_abs_with_kids,
|
||||||
|
test_lying_cache_liar
|
||||||
];
|
];
|
||||||
var numtests = tests.length;
|
var numtests = tests.length;
|
||||||
function runNextTest(err) {
|
function runNextTest(err) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user