summaryrefslogtreecommitdiff
path: root/lib/fs.js
diff options
context:
space:
mode:
authorBrian White <mscdex@mscdex.net>2017-03-11 19:41:20 -0500
committerJames M Snell <jasnell@gmail.com>2017-03-14 21:54:40 -0700
commit6a5ab5d550719f416683ec0d588461b8bc9a8787 (patch)
treed76b6d99dfa477021a8bcf8e7d0dbbc90fb3e23f /lib/fs.js
parent3d7245c0989878dc708df06151fa2426c251a03a (diff)
downloadnode-new-6a5ab5d550719f416683ec0d588461b8bc9a8787.tar.gz
fs: include more fs.stat*() optimizations
Including: * Move async *stat() functions to FillStatsArray() now used by the sync *stat() functions * Avoid creating fs.Stats instances for implicit async/sync *stat() calls used in various fs functions * Store reference to Float64Array data on C++ side for easier/faster access, instead of passing from JS to C++ on every async/sync *stat() call PR-URL: https://github.com/nodejs/node/pull/11665 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Diffstat (limited to 'lib/fs.js')
-rw-r--r--lib/fs.js153
1 files changed, 100 insertions, 53 deletions
diff --git a/lib/fs.js b/lib/fs.js
index d8ae0b09ba..7b50412ea7 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -25,6 +25,7 @@
'use strict';
const constants = process.binding('constants').fs;
+const { S_IFMT, S_IFREG, S_IFLNK } = constants;
const util = require('util');
const pathModule = require('path');
const { isUint8Array } = process.binding('util');
@@ -135,6 +136,24 @@ function makeCallback(cb) {
};
}
+// Special case of `makeCallback()` that is specific to async `*stat()` calls as
+// an optimization, since the data passed back to the callback needs to be
+// transformed anyway.
+function makeStatsCallback(cb) {
+ if (cb === undefined) {
+ return rethrow();
+ }
+
+ if (typeof cb !== 'function') {
+ throw new TypeError('"callback" argument must be a function');
+ }
+
+ return function(err) {
+ if (err) return cb(err);
+ cb(err, statsFromValues());
+ };
+}
+
function nullCheck(path, callback) {
if (('' + path).indexOf('\u0000') !== -1) {
var er = new Error('Path must be a string without null bytes');
@@ -151,7 +170,7 @@ function isFd(path) {
return (path >>> 0) === path;
}
-// Static method to set the stats properties on a Stats object.
+// Constructor for file stats.
function Stats(
dev,
mode,
@@ -184,41 +203,49 @@ function Stats(
}
fs.Stats = Stats;
-// Create a C++ binding to the function which creates a Stats object.
-binding.FSInitialize(fs.Stats);
-
-fs.Stats.prototype._checkModeProperty = function(property) {
- return ((this.mode & constants.S_IFMT) === property);
+Stats.prototype._checkModeProperty = function(property) {
+ return ((this.mode & S_IFMT) === property);
};
-fs.Stats.prototype.isDirectory = function() {
+Stats.prototype.isDirectory = function() {
return this._checkModeProperty(constants.S_IFDIR);
};
-fs.Stats.prototype.isFile = function() {
- return this._checkModeProperty(constants.S_IFREG);
+Stats.prototype.isFile = function() {
+ return this._checkModeProperty(S_IFREG);
};
-fs.Stats.prototype.isBlockDevice = function() {
+Stats.prototype.isBlockDevice = function() {
return this._checkModeProperty(constants.S_IFBLK);
};
-fs.Stats.prototype.isCharacterDevice = function() {
+Stats.prototype.isCharacterDevice = function() {
return this._checkModeProperty(constants.S_IFCHR);
};
-fs.Stats.prototype.isSymbolicLink = function() {
- return this._checkModeProperty(constants.S_IFLNK);
+Stats.prototype.isSymbolicLink = function() {
+ return this._checkModeProperty(S_IFLNK);
};
-fs.Stats.prototype.isFIFO = function() {
+Stats.prototype.isFIFO = function() {
return this._checkModeProperty(constants.S_IFIFO);
};
-fs.Stats.prototype.isSocket = function() {
+Stats.prototype.isSocket = function() {
return this._checkModeProperty(constants.S_IFSOCK);
};
+const statValues = binding.getStatValues();
+
+function statsFromValues() {
+ return new Stats(statValues[0], statValues[1], statValues[2], statValues[3],
+ statValues[4], statValues[5],
+ statValues[6] < 0 ? undefined : statValues[6], statValues[7],
+ statValues[8], statValues[9] < 0 ? undefined : statValues[9],
+ statValues[10], statValues[11], statValues[12],
+ statValues[13]);
+}
+
// Don't allow mode to accidentally be overwritten.
['F_OK', 'R_OK', 'W_OK', 'X_OK'].forEach(function(key) {
Object.defineProperty(fs, key, {
@@ -275,7 +302,7 @@ fs.exists = function(path, callback) {
var req = new FSReqWrap();
req.oncomplete = cb;
binding.stat(pathModule._makeLong(path), req);
- function cb(err, stats) {
+ function cb(err) {
if (callback) callback(err ? false : true);
}
};
@@ -284,7 +311,7 @@ fs.existsSync = function(path) {
try {
handleError((path = getPathFromURL(path)));
nullCheck(path);
- binding.stat(pathModule._makeLong(path), statValues);
+ binding.stat(pathModule._makeLong(path));
return true;
} catch (e) {
return false;
@@ -387,13 +414,19 @@ function readFileAfterOpen(err, fd) {
binding.fstat(fd, req);
}
-function readFileAfterStat(err, st) {
+function readFileAfterStat(err) {
var context = this.context;
if (err)
return context.close(err);
- var size = context.size = st.isFile() ? st.size : 0;
+ // Use stats array directly to avoid creating an fs.Stats instance just for
+ // our internal use.
+ var size;
+ if ((statValues[1/*mode*/] & S_IFMT) === S_IFREG)
+ size = context.size = statValues[8/*size*/];
+ else
+ size = context.size = 0;
if (size === 0) {
context.buffers = [];
@@ -467,14 +500,13 @@ function tryToString(buf, encoding, callback) {
function tryStatSync(fd, isUserFd) {
var threw = true;
- var st;
try {
- st = fs.fstatSync(fd);
+ binding.fstat(fd);
threw = false;
} finally {
if (threw && !isUserFd) fs.closeSync(fd);
}
- return st;
+ return !threw;
}
function tryCreateBuffer(size, fd, isUserFd) {
@@ -506,8 +538,13 @@ fs.readFileSync = function(path, options) {
var isUserFd = isFd(path); // file descriptor ownership
var fd = isUserFd ? path : fs.openSync(path, options.flag || 'r', 0o666);
- var st = tryStatSync(fd, isUserFd);
- var size = st.isFile() ? st.size : 0;
+ // Use stats array directly to avoid creating an fs.Stats instance just for
+ // our internal use.
+ var size;
+ if (tryStatSync(fd, isUserFd) && (statValues[1/*mode*/] & S_IFMT) === S_IFREG)
+ size = statValues[8/*size*/];
+ else
+ size = 0;
var pos = 0;
var buffer; // single buffer with file data
var buffers; // list for when size is unknown
@@ -854,12 +891,12 @@ fs.readdirSync = function(path, options) {
fs.fstat = function(fd, callback) {
var req = new FSReqWrap();
- req.oncomplete = makeCallback(callback);
+ req.oncomplete = makeStatsCallback(callback);
binding.fstat(fd, req);
};
fs.lstat = function(path, callback) {
- callback = makeCallback(callback);
+ callback = makeStatsCallback(callback);
if (handleError((path = getPathFromURL(path)), callback))
return;
if (!nullCheck(path, callback)) return;
@@ -869,7 +906,7 @@ fs.lstat = function(path, callback) {
};
fs.stat = function(path, callback) {
- callback = makeCallback(callback);
+ callback = makeStatsCallback(callback);
if (handleError((path = getPathFromURL(path)), callback))
return;
if (!nullCheck(path, callback)) return;
@@ -878,32 +915,22 @@ fs.stat = function(path, callback) {
binding.stat(pathModule._makeLong(path), req);
};
-const statValues = new Float64Array(14);
-function statsFromValues() {
- return new Stats(statValues[0], statValues[1], statValues[2], statValues[3],
- statValues[4], statValues[5],
- statValues[6] < 0 ? undefined : statValues[6], statValues[7],
- statValues[8], statValues[9] < 0 ? undefined : statValues[9],
- statValues[10], statValues[11], statValues[12],
- statValues[13]);
-}
-
fs.fstatSync = function(fd) {
- binding.fstat(fd, statValues);
+ binding.fstat(fd);
return statsFromValues();
};
fs.lstatSync = function(path) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
- binding.lstat(pathModule._makeLong(path), statValues);
+ binding.lstat(pathModule._makeLong(path));
return statsFromValues();
};
fs.statSync = function(path) {
handleError((path = getPathFromURL(path)));
nullCheck(path);
- binding.stat(pathModule._makeLong(path), statValues);
+ binding.stat(pathModule._makeLong(path));
return statsFromValues();
};
@@ -1380,6 +1407,15 @@ function emitStop(self) {
self.emit('stop');
}
+function statsFromPrevValues() {
+ return new Stats(statValues[14], statValues[15], statValues[16],
+ statValues[17], statValues[18], statValues[19],
+ statValues[20] < 0 ? undefined : statValues[20],
+ statValues[21], statValues[22],
+ statValues[23] < 0 ? undefined : statValues[23],
+ statValues[24], statValues[25], statValues[26],
+ statValues[27]);
+}
function StatWatcher() {
EventEmitter.call(this);
@@ -1390,13 +1426,13 @@ function StatWatcher() {
// the sake of backwards compatibility
var oldStatus = -1;
- this._handle.onchange = function(current, previous, newStatus) {
+ this._handle.onchange = function(newStatus) {
if (oldStatus === -1 &&
newStatus === -1 &&
- current.nlink === previous.nlink) return;
+ statValues[2/*new nlink*/] === statValues[16/*old nlink*/]) return;
oldStatus = newStatus;
- self.emit('change', current, previous);
+ self.emit('change', statsFromValues(), statsFromPrevValues());
};
this._handle.onstop = function() {
@@ -1544,7 +1580,7 @@ fs.realpathSync = function realpathSync(p, options) {
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
- fs.lstatSync(base);
+ binding.lstat(pathModule._makeLong(base));
knownHard[base] = true;
}
@@ -1576,8 +1612,12 @@ fs.realpathSync = function realpathSync(p, options) {
if (maybeCachedResolved) {
resolvedLink = maybeCachedResolved;
} else {
- var stat = fs.lstatSync(base);
- if (!stat.isSymbolicLink()) {
+ // Use stats array directly to avoid creating an fs.Stats instance just
+ // for our internal use.
+
+ binding.lstat(pathModule._makeLong(base));
+
+ if ((statValues[1/*mode*/] & S_IFMT) !== S_IFLNK) {
knownHard[base] = true;
if (cache) cache.set(base, base);
continue;
@@ -1588,14 +1628,16 @@ fs.realpathSync = function realpathSync(p, options) {
var linkTarget = null;
var id;
if (!isWindows) {
- id = `${stat.dev.toString(32)}:${stat.ino.toString(32)}`;
+ var dev = statValues[0/*dev*/].toString(32);
+ var ino = statValues[7/*ino*/].toString(32);
+ id = `${dev}:${ino}`;
if (seenLinks.hasOwnProperty(id)) {
linkTarget = seenLinks[id];
}
}
if (linkTarget === null) {
- fs.statSync(base);
- linkTarget = fs.readlinkSync(base);
+ binding.stat(pathModule._makeLong(base));
+ linkTarget = binding.readlink(pathModule._makeLong(base));
}
resolvedLink = pathModule.resolve(previous, linkTarget);
@@ -1614,7 +1656,7 @@ fs.realpathSync = function realpathSync(p, options) {
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
- fs.lstatSync(base);
+ binding.lstat(pathModule._makeLong(base));
knownHard[base] = true;
}
}
@@ -1694,11 +1736,14 @@ fs.realpath = function realpath(p, options, callback) {
return fs.lstat(base, gotStat);
}
- function gotStat(err, stat) {
+ function gotStat(err) {
if (err) return callback(err);
+ // Use stats array directly to avoid creating an fs.Stats instance just for
+ // our internal use.
+
// if not a symlink, skip to the next path part
- if (!stat.isSymbolicLink()) {
+ if ((statValues[1/*mode*/] & S_IFMT) !== S_IFLNK) {
knownHard[base] = true;
return process.nextTick(LOOP);
}
@@ -1708,7 +1753,9 @@ fs.realpath = function realpath(p, options, callback) {
// dev/ino always return 0 on windows, so skip the check.
let id;
if (!isWindows) {
- id = `${stat.dev.toString(32)}:${stat.ino.toString(32)}`;
+ var dev = statValues[0/*ino*/].toString(32);
+ var ino = statValues[7/*ino*/].toString(32);
+ id = `${dev}:${ino}`;
if (seenLinks.hasOwnProperty(id)) {
return gotTarget(null, seenLinks[id], base);
}