diff options
author | Brian White <mscdex@mscdex.net> | 2017-03-11 19:41:20 -0500 |
---|---|---|
committer | James M Snell <jasnell@gmail.com> | 2017-03-14 21:54:40 -0700 |
commit | 6a5ab5d550719f416683ec0d588461b8bc9a8787 (patch) | |
tree | d76b6d99dfa477021a8bcf8e7d0dbbc90fb3e23f /lib/fs.js | |
parent | 3d7245c0989878dc708df06151fa2426c251a03a (diff) | |
download | node-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.js | 153 |
1 files changed, 100 insertions, 53 deletions
@@ -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); } |