Skip to content

Commit b488b19

Browse files
jhamhadermscdex
authored andcommitted
fs: optimize realpath using uv_fs_realpath()
Remove realpath() and realpathSync() cache. Use the native uv_fs_realpath() which is faster then the JS implementation by a few orders of magnitude. PR-URL: nodejs#3594 Reviewed-By: Trevor Norris <[email protected]> Reviewed-By: Brian White <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Johan Bergström <[email protected]>
1 parent 81fd458 commit b488b19

File tree

6 files changed

+134
-329
lines changed

6 files changed

+134
-329
lines changed

doc/api/fs.markdown

+17-19
Original file line numberDiff line numberDiff line change
@@ -916,26 +916,20 @@ object with an `encoding` property specifying the character encoding to use for
916916
the link path passed to the callback. If the `encoding` is set to `'buffer'`,
917917
the link path returned will be passed as a `Buffer` object.
918918

919-
## fs.realpath(path[, cache], callback)
919+
## fs.realpath(path[, options], callback)
920920

921921
* `path` {String | Buffer}
922-
* `cache` {Object}
922+
* `options` {String | Object}
923+
* `encoding` {String} default = `'utf8'`
923924
* `callback` {Function}
924925

925926
Asynchronous realpath(2). The `callback` gets two arguments `(err,
926-
resolvedPath)`. May use `process.cwd` to resolve relative paths. `cache` is an
927-
object literal of mapped paths that can be used to force a specific path
928-
resolution or avoid additional `fs.stat` calls for known real paths.
929-
930-
Example:
927+
resolvedPath)`. May use `process.cwd` to resolve relative paths.
931928

932-
```js
933-
var cache = {'/etc':'/private/etc'};
934-
fs.realpath('/etc/passwd', cache, (err, resolvedPath) => {
935-
if (err) throw err;
936-
console.log(resolvedPath);
937-
});
938-
```
929+
The optional `options` argument can be a string specifying an encoding, or an
930+
object with an `encoding` property specifying the character encoding to use for
931+
the path passed to the callback. If the `encoding` is set to `'buffer'`,
932+
the path returned will be passed as a `Buffer` object.
939933

940934
## fs.readSync(fd, buffer, offset, length, position)
941935

@@ -947,14 +941,18 @@ fs.realpath('/etc/passwd', cache, (err, resolvedPath) => {
947941

948942
Synchronous version of [`fs.read()`][]. Returns the number of `bytesRead`.
949943

950-
## fs.realpathSync(path[, cache])
944+
## fs.realpathSync(path[, options])
951945

952946
* `path` {String | Buffer};
953-
* `cache` {Object}
947+
* `options` {String | Object}
948+
* `encoding` {String} default = `'utf8'`
954949

955-
Synchronous realpath(2). Returns the resolved path. `cache` is an
956-
object literal of mapped paths that can be used to force a specific path
957-
resolution or avoid additional `fs.stat` calls for known real paths.
950+
Synchronous realpath(2). Returns the resolved path.
951+
952+
The optional `options` argument can be a string specifying an encoding, or an
953+
object with an `encoding` property specifying the character encoding to use for
954+
the path passed to the callback. If the `encoding` is set to `'buffer'`,
955+
the path returned will be passed as a `Buffer` object.
958956

959957
## fs.rename(oldPath, newPath, callback)
960958

lib/fs.js

+26-223
Original file line numberDiff line numberDiff line change
@@ -1557,234 +1557,37 @@ fs.unwatchFile = function(filename, listener) {
15571557
}
15581558
};
15591559

1560-
// Regexp that finds the next partion of a (partial) path
1561-
// result is [base_with_slash, base], e.g. ['somedir/', 'somedir']
1562-
const nextPartRe = isWindows ?
1563-
/(.*?)(?:[\/\\]+|$)/g :
1564-
/(.*?)(?:[\/]+|$)/g;
1565-
1566-
// Regex to find the device root, including trailing slash. E.g. 'c:\\'.
1567-
const splitRootRe = isWindows ?
1568-
/^(?:[a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?[\\\/]*/ :
1569-
/^[\/]*/;
1570-
1571-
fs.realpathSync = function realpathSync(p, cache) {
1572-
// make p is absolute
1573-
p = pathModule.resolve(p);
1574-
1575-
if (cache && Object.prototype.hasOwnProperty.call(cache, p)) {
1576-
return cache[p];
1577-
}
1578-
1579-
const original = p;
1580-
const seenLinks = {};
1581-
const knownHard = {};
1582-
1583-
// current character position in p
1584-
var pos;
1585-
// the partial path so far, including a trailing slash if any
1586-
var current;
1587-
// the partial path without a trailing slash (except when pointing at a root)
1588-
var base;
1589-
// the partial path scanned in the previous round, with slash
1590-
var previous;
1591-
1592-
start();
1593-
1594-
function start() {
1595-
// Skip over roots
1596-
var m = splitRootRe.exec(p);
1597-
pos = m[0].length;
1598-
current = m[0];
1599-
base = m[0];
1600-
previous = '';
1601-
1602-
// On windows, check that the root exists. On unix there is no need.
1603-
if (isWindows && !knownHard[base]) {
1604-
fs.lstatSync(base);
1605-
knownHard[base] = true;
1606-
}
1607-
}
1608-
1609-
// walk down the path, swapping out linked pathparts for their real
1610-
// values
1611-
// NB: p.length changes.
1612-
while (pos < p.length) {
1613-
// find the next part
1614-
nextPartRe.lastIndex = pos;
1615-
var result = nextPartRe.exec(p);
1616-
previous = current;
1617-
current += result[0];
1618-
base = previous + result[1];
1619-
pos = nextPartRe.lastIndex;
1620-
1621-
// continue if not a symlink
1622-
if (knownHard[base] || (cache && cache[base] === base)) {
1623-
continue;
1624-
}
1625-
1626-
var resolvedLink;
1627-
if (cache && Object.prototype.hasOwnProperty.call(cache, base)) {
1628-
// some known symbolic link. no need to stat again.
1629-
resolvedLink = cache[base];
1630-
} else {
1631-
var stat = fs.lstatSync(base);
1632-
if (!stat.isSymbolicLink()) {
1633-
knownHard[base] = true;
1634-
if (cache) cache[base] = base;
1635-
continue;
1636-
}
1637-
1638-
// read the link if it wasn't read before
1639-
// dev/ino always return 0 on windows, so skip the check.
1640-
var linkTarget = null;
1641-
if (!isWindows) {
1642-
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
1643-
if (seenLinks.hasOwnProperty(id)) {
1644-
linkTarget = seenLinks[id];
1645-
}
1646-
}
1647-
if (linkTarget === null) {
1648-
fs.statSync(base);
1649-
linkTarget = fs.readlinkSync(base);
1650-
}
1651-
resolvedLink = pathModule.resolve(previous, linkTarget);
1652-
// track this, if given a cache.
1653-
if (cache) cache[base] = resolvedLink;
1654-
if (!isWindows) seenLinks[id] = linkTarget;
1655-
}
16561560

1657-
// resolve the link, then start over
1658-
p = pathModule.resolve(resolvedLink, p.slice(pos));
1659-
start();
1660-
}
1661-
1662-
if (cache) cache[original] = p;
1663-
1664-
return p;
1561+
fs.realpathSync = function realpathSync(path, options) {
1562+
if (!options)
1563+
options = {};
1564+
else if (typeof options === 'string')
1565+
options = {encoding: options};
1566+
else if (typeof options !== 'object')
1567+
throw new TypeError('"options" must be a string or an object');
1568+
nullCheck(path);
1569+
return binding.realpath(pathModule._makeLong(path), options.encoding);
16651570
};
16661571

16671572

1668-
fs.realpath = function realpath(p, cache, cb) {
1669-
if (typeof cb !== 'function') {
1670-
cb = maybeCallback(cache);
1671-
cache = null;
1672-
}
1673-
1674-
// make p is absolute
1675-
p = pathModule.resolve(p);
1676-
1677-
if (cache && Object.prototype.hasOwnProperty.call(cache, p)) {
1678-
return process.nextTick(cb.bind(null, null, cache[p]));
1679-
}
1680-
1681-
const original = p;
1682-
const seenLinks = {};
1683-
const knownHard = {};
1684-
1685-
// current character position in p
1686-
var pos;
1687-
// the partial path so far, including a trailing slash if any
1688-
var current;
1689-
// the partial path without a trailing slash (except when pointing at a root)
1690-
var base;
1691-
// the partial path scanned in the previous round, with slash
1692-
var previous;
1693-
1694-
start();
1695-
1696-
function start() {
1697-
// Skip over roots
1698-
var m = splitRootRe.exec(p);
1699-
pos = m[0].length;
1700-
current = m[0];
1701-
base = m[0];
1702-
previous = '';
1703-
1704-
// On windows, check that the root exists. On unix there is no need.
1705-
if (isWindows && !knownHard[base]) {
1706-
fs.lstat(base, function(err) {
1707-
if (err) return cb(err);
1708-
knownHard[base] = true;
1709-
LOOP();
1710-
});
1711-
} else {
1712-
process.nextTick(LOOP);
1713-
}
1714-
}
1715-
1716-
// walk down the path, swapping out linked pathparts for their real
1717-
// values
1718-
function LOOP() {
1719-
// stop if scanned past end of path
1720-
if (pos >= p.length) {
1721-
if (cache) cache[original] = p;
1722-
return cb(null, p);
1723-
}
1724-
1725-
// find the next part
1726-
nextPartRe.lastIndex = pos;
1727-
var result = nextPartRe.exec(p);
1728-
previous = current;
1729-
current += result[0];
1730-
base = previous + result[1];
1731-
pos = nextPartRe.lastIndex;
1732-
1733-
// continue if not a symlink
1734-
if (knownHard[base] || (cache && cache[base] === base)) {
1735-
return process.nextTick(LOOP);
1736-
}
1737-
1738-
if (cache && Object.prototype.hasOwnProperty.call(cache, base)) {
1739-
// known symbolic link. no need to stat again.
1740-
return gotResolvedLink(cache[base]);
1741-
}
1742-
1743-
return fs.lstat(base, gotStat);
1744-
}
1745-
1746-
function gotStat(err, stat) {
1747-
if (err) return cb(err);
1748-
1749-
// if not a symlink, skip to the next path part
1750-
if (!stat.isSymbolicLink()) {
1751-
knownHard[base] = true;
1752-
if (cache) cache[base] = base;
1753-
return process.nextTick(LOOP);
1754-
}
1755-
1756-
// stat & read the link if not read before
1757-
// call gotTarget as soon as the link target is known
1758-
// dev/ino always return 0 on windows, so skip the check.
1759-
if (!isWindows) {
1760-
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
1761-
if (seenLinks.hasOwnProperty(id)) {
1762-
return gotTarget(null, seenLinks[id], base);
1763-
}
1764-
}
1765-
fs.stat(base, function(err) {
1766-
if (err) return cb(err);
1767-
1768-
fs.readlink(base, function(err, target) {
1769-
if (!isWindows) seenLinks[id] = target;
1770-
gotTarget(err, target);
1771-
});
1772-
});
1773-
}
1774-
1775-
function gotTarget(err, target, base) {
1776-
if (err) return cb(err);
1777-
1778-
var resolvedLink = pathModule.resolve(previous, target);
1779-
if (cache) cache[base] = resolvedLink;
1780-
gotResolvedLink(resolvedLink);
1781-
}
1782-
1783-
function gotResolvedLink(resolvedLink) {
1784-
// resolve the link, then start over
1785-
p = pathModule.resolve(resolvedLink, p.slice(pos));
1786-
start();
1573+
fs.realpath = function realpath(path, options, callback) {
1574+
if (!options) {
1575+
options = {};
1576+
} else if (typeof options === 'function') {
1577+
callback = options;
1578+
options = {};
1579+
} else if (typeof options === 'string') {
1580+
options = {encoding: options};
1581+
} else if (typeof options !== 'object') {
1582+
throw new TypeError('"options" must be a string or an object');
17871583
}
1584+
callback = makeCallback(callback);
1585+
if (!nullCheck(path, callback))
1586+
return;
1587+
var req = new FSReqWrap();
1588+
req.oncomplete = callback;
1589+
binding.realpath(pathModule._makeLong(path), options.encoding, req);
1590+
return;
17881591
};
17891592

17901593

lib/module.js

+2-11
Original file line numberDiff line numberDiff line change
@@ -108,19 +108,10 @@ function tryPackage(requestPath, exts) {
108108
tryExtensions(path.resolve(filename, 'index'), exts);
109109
}
110110

111-
// In order to minimize unnecessary lstat() calls,
112-
// this cache is a list of known-real paths.
113-
// Set to an empty object to reset.
114-
Module._realpathCache = {};
115-
116111
// check if the file exists and is not a directory
117112
function tryFile(requestPath) {
118113
const rc = stat(requestPath);
119-
return rc === 0 && toRealPath(requestPath);
120-
}
121-
122-
function toRealPath(requestPath) {
123-
return fs.realpathSync(requestPath, Module._realpathCache);
114+
return rc === 0 && fs.realpathSync(requestPath);
124115
}
125116

126117
// given a path check a the file exists with any of the set extensions
@@ -163,7 +154,7 @@ Module._findPath = function(request, paths) {
163154
if (!trailingSlash) {
164155
const rc = stat(basePath);
165156
if (rc === 0) { // File.
166-
filename = toRealPath(basePath);
157+
filename = fs.realpathSync(basePath);
167158
} else if (rc === 1) { // Directory.
168159
if (exts === undefined)
169160
exts = Object.keys(Module._extensions);

0 commit comments

Comments
 (0)