summaryrefslogtreecommitdiff
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
parent3d7245c0989878dc708df06151fa2426c251a03a (diff)
downloadandroid-node-v8-6a5ab5d550719f416683ec0d588461b8bc9a8787.tar.gz
android-node-v8-6a5ab5d550719f416683ec0d588461b8bc9a8787.tar.bz2
android-node-v8-6a5ab5d550719f416683ec0d588461b8bc9a8787.zip
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>
-rw-r--r--benchmark/fs/bench-stat.js22
-rw-r--r--benchmark/fs/bench-statSync.js37
-rw-r--r--benchmark/fs/readFileSync.js17
-rw-r--r--lib/fs.js153
-rw-r--r--src/env-inl.h10
-rw-r--r--src/env.h6
-rw-r--r--src/node_file.cc189
-rw-r--r--src/node_internals.h2
-rw-r--r--src/node_stat_watcher.cc11
-rw-r--r--test/parallel/test-fs-sync-fd-leak.js2
10 files changed, 205 insertions, 244 deletions
diff --git a/benchmark/fs/bench-stat.js b/benchmark/fs/bench-stat.js
index c0db00e27d..149b4f3d3b 100644
--- a/benchmark/fs/bench-stat.js
+++ b/benchmark/fs/bench-stat.js
@@ -4,20 +4,30 @@ const common = require('../common');
const fs = require('fs');
const bench = common.createBenchmark(main, {
- n: [1e4],
- kind: ['lstat', 'stat']
+ n: [20e4],
+ kind: ['fstat', 'lstat', 'stat']
});
function main(conf) {
const n = conf.n >>> 0;
+ const kind = conf.kind;
+ var arg;
+ if (kind === 'fstat')
+ arg = fs.openSync(__filename, 'r');
+ else
+ arg = __filename;
bench.start();
(function r(cntr, fn) {
- if (cntr-- <= 0)
- return bench.end(n);
- fn(__filename, function() {
+ if (cntr-- <= 0) {
+ bench.end(n);
+ if (kind === 'fstat')
+ fs.closeSync(arg);
+ return;
+ }
+ fn(arg, function() {
r(cntr, fn);
});
- }(n, fs[conf.kind]));
+ }(n, fs[kind]));
}
diff --git a/benchmark/fs/bench-statSync.js b/benchmark/fs/bench-statSync.js
index 4bc2ecd65a..57e03df3b0 100644
--- a/benchmark/fs/bench-statSync.js
+++ b/benchmark/fs/bench-statSync.js
@@ -4,36 +4,23 @@ const common = require('../common');
const fs = require('fs');
const bench = common.createBenchmark(main, {
- n: [1e4],
+ n: [1e6],
kind: ['fstatSync', 'lstatSync', 'statSync']
});
function main(conf) {
const n = conf.n >>> 0;
- var fn;
- var i;
- switch (conf.kind) {
- case 'statSync':
- case 'lstatSync':
- fn = fs[conf.kind];
- bench.start();
- for (i = 0; i < n; i++) {
- fn(__filename);
- }
- bench.end(n);
- break;
- case 'fstatSync':
- fn = fs.fstatSync;
- const fd = fs.openSync(__filename, 'r');
- bench.start();
- for (i = 0; i < n; i++) {
- fn(fd);
- }
- bench.end(n);
- fs.closeSync(fd);
- break;
- default:
- throw new Error('Invalid kind argument');
+ const kind = conf.kind;
+ const arg = (kind === 'fstatSync' ? fs.openSync(__filename, 'r') : __dirname);
+ const fn = fs[conf.kind];
+
+ bench.start();
+ for (var i = 0; i < n; i++) {
+ fn(arg);
}
+ bench.end(n);
+
+ if (kind === 'fstat')
+ fs.closeSync(arg);
}
diff --git a/benchmark/fs/readFileSync.js b/benchmark/fs/readFileSync.js
new file mode 100644
index 0000000000..7c8f5d240d
--- /dev/null
+++ b/benchmark/fs/readFileSync.js
@@ -0,0 +1,17 @@
+'use strict';
+
+var common = require('../common.js');
+var fs = require('fs');
+
+var bench = common.createBenchmark(main, {
+ n: [60e4]
+});
+
+function main(conf) {
+ var n = +conf.n;
+
+ bench.start();
+ for (var i = 0; i < n; ++i)
+ fs.readFileSync(__filename);
+ bench.end(n);
+}
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);
}
diff --git a/src/env-inl.h b/src/env-inl.h
index 06a9474c9d..978f8ca819 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -199,6 +199,7 @@ inline Environment::Environment(IsolateData* isolate_data,
#endif
handle_cleanup_waiting_(0),
http_parser_buffer_(nullptr),
+ fs_stats_field_array_(nullptr),
context_(context->GetIsolate(), context) {
// We'll be creating new objects so make sure we've entered the context.
v8::HandleScope handle_scope(isolate());
@@ -380,6 +381,15 @@ inline void Environment::set_http_parser_buffer(char* buffer) {
http_parser_buffer_ = buffer;
}
+inline double* Environment::fs_stats_field_array() const {
+ return fs_stats_field_array_;
+}
+
+inline void Environment::set_fs_stats_field_array(double* fields) {
+ CHECK_EQ(fs_stats_field_array_, nullptr); // Should be set only once.
+ fs_stats_field_array_ = fields;
+}
+
inline Environment* Environment::from_cares_timer_handle(uv_timer_t* handle) {
return ContainerOf(&Environment::cares_timer_handle_, handle);
}
diff --git a/src/env.h b/src/env.h
index 06cc517435..abb0e6d0e5 100644
--- a/src/env.h
+++ b/src/env.h
@@ -253,7 +253,6 @@ namespace node {
V(context, v8::Context) \
V(domain_array, v8::Array) \
V(domains_stack_array, v8::Array) \
- V(fs_stats_constructor_function, v8::Function) \
V(generic_internal_field_template, v8::ObjectTemplate) \
V(jsstream_constructor_template, v8::FunctionTemplate) \
V(module_load_list_array, v8::Array) \
@@ -492,6 +491,9 @@ class Environment {
inline char* http_parser_buffer() const;
inline void set_http_parser_buffer(char* buffer);
+ inline double* fs_stats_field_array() const;
+ inline void set_fs_stats_field_array(double* fields);
+
inline void ThrowError(const char* errmsg);
inline void ThrowTypeError(const char* errmsg);
inline void ThrowRangeError(const char* errmsg);
@@ -600,6 +602,8 @@ class Environment {
char* http_parser_buffer_;
+ double* fs_stats_field_array_;
+
#define V(PropertyName, TypeName) \
v8::Persistent<TypeName> PropertyName ## _;
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
diff --git a/src/node_file.cc b/src/node_file.cc
index 8e738fb8bf..9c180833fb 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -50,7 +50,6 @@ namespace node {
using v8::Array;
using v8::ArrayBuffer;
using v8::Context;
-using v8::EscapableHandleScope;
using v8::Float64Array;
using v8::Function;
using v8::FunctionCallbackInfo;
@@ -211,6 +210,14 @@ static void After(uv_fs_t *req) {
argc = 1;
break;
+ case UV_FS_STAT:
+ case UV_FS_LSTAT:
+ case UV_FS_FSTAT:
+ argc = 1;
+ FillStatsArray(env->fs_stats_field_array(),
+ static_cast<const uv_stat_t*>(req->ptr));
+ break;
+
case UV_FS_UTIME:
case UV_FS_FUTIME:
argc = 0;
@@ -224,13 +231,6 @@ static void After(uv_fs_t *req) {
argv[1] = Integer::New(env->isolate(), req->result);
break;
- case UV_FS_STAT:
- case UV_FS_LSTAT:
- case UV_FS_FSTAT:
- argv[1] = BuildStatsObject(env,
- static_cast<const uv_stat_t*>(req->ptr));
- break;
-
case UV_FS_MKDTEMP:
link = StringBytes::Encode(env->isolate(),
static_cast<const char*>(req->path),
@@ -441,116 +441,6 @@ static void Close(const FunctionCallbackInfo<Value>& args) {
}
-Local<Value> BuildStatsObject(Environment* env, const uv_stat_t* s) {
- EscapableHandleScope handle_scope(env->isolate());
-
- // If you hit this assertion, you forgot to enter the v8::Context first.
- CHECK_EQ(env->context(), env->isolate()->GetCurrentContext());
-
- // The code below is very nasty-looking but it prevents a segmentation fault
- // when people run JS code like the snippet below. It's apparently more
- // common than you would expect, several people have reported this crash...
- //
- // function crash() {
- // fs.statSync('.');
- // crash();
- // }
- //
- // We need to check the return value of Number::New() and Date::New()
- // and make sure that we bail out when V8 returns an empty handle.
-
- // Unsigned integers. It does not actually seem to be specified whether
- // uid and gid are unsigned or not, but in practice they are unsigned,
- // and Node’s (F)Chown functions do check their arguments for unsignedness.
-#define X(name) \
- Local<Value> name = Integer::NewFromUnsigned(env->isolate(), s->st_##name); \
- if (name.IsEmpty()) \
- return Local<Object>(); \
-
- X(uid)
- X(gid)
-# if defined(__POSIX__)
- X(blksize)
-# else
- Local<Value> blksize = Undefined(env->isolate());
-# endif
-#undef X
-
- // Integers.
-#define X(name) \
- Local<Value> name = Integer::New(env->isolate(), s->st_##name); \
- if (name.IsEmpty()) \
- return Local<Object>(); \
-
- X(dev)
- X(mode)
- X(nlink)
- X(rdev)
-#undef X
-
- // Numbers.
-#define X(name) \
- Local<Value> name = Number::New(env->isolate(), \
- static_cast<double>(s->st_##name)); \
- if (name.IsEmpty()) \
- return Local<Object>(); \
-
- X(ino)
- X(size)
-# if defined(__POSIX__)
- X(blocks)
-# else
- Local<Value> blocks = Undefined(env->isolate());
-# endif
-#undef X
-
- // Dates.
-#define X(name) \
- Local<Value> name##_msec = \
- Number::New(env->isolate(), \
- (static_cast<double>(s->st_##name.tv_sec) * 1000) + \
- (static_cast<double>(s->st_##name.tv_nsec / 1000000))); \
- \
- if (name##_msec.IsEmpty()) \
- return Local<Object>(); \
-
- X(atim)
- X(mtim)
- X(ctim)
- X(birthtim)
-#undef X
-
- // Pass stats as the first argument, this is the object we are modifying.
- Local<Value> argv[] = {
- dev,
- mode,
- nlink,
- uid,
- gid,
- rdev,
- blksize,
- ino,
- size,
- blocks,
- atim_msec,
- mtim_msec,
- ctim_msec,
- birthtim_msec
- };
-
- // Call out to JavaScript to create the stats object.
- Local<Value> stats =
- env->fs_stats_constructor_function()->NewInstance(
- env->context(),
- arraysize(argv),
- argv).FromMaybe(Local<Value>());
-
- if (stats.IsEmpty())
- return handle_scope.Escape(Local<Object>());
-
- return handle_scope.Escape(stats);
-}
-
void FillStatsArray(double* fields, const uv_stat_t* s) {
fields[0] = s->st_dev;
fields[1] = s->st_mode;
@@ -666,15 +556,12 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
BufferValue path(env->isolate(), args[0]);
ASSERT_PATH(path)
- if (args[1]->IsFloat64Array()) {
- Local<Float64Array> array = args[1].As<Float64Array>();
- CHECK_EQ(array->Length(), 14);
- Local<ArrayBuffer> ab = array->Buffer();
- double* fields = static_cast<double*>(ab->GetContents().Data());
- SYNC_CALL(stat, *path, *path)
- FillStatsArray(fields, static_cast<const uv_stat_t*>(SYNC_REQ.ptr));
- } else if (args[1]->IsObject()) {
+ if (args[1]->IsObject()) {
ASYNC_CALL(stat, args[1], UTF8, *path)
+ } else {
+ SYNC_CALL(stat, *path, *path)
+ FillStatsArray(env->fs_stats_field_array(),
+ static_cast<const uv_stat_t*>(SYNC_REQ.ptr));
}
}
@@ -687,15 +574,12 @@ static void LStat(const FunctionCallbackInfo<Value>& args) {
BufferValue path(env->isolate(), args[0]);
ASSERT_PATH(path)
- if (args[1]->IsFloat64Array()) {
- Local<Float64Array> array = args[1].As<Float64Array>();
- CHECK_EQ(array->Length(), 14);
- Local<ArrayBuffer> ab = array->Buffer();
- double* fields = static_cast<double*>(ab->GetContents().Data());
- SYNC_CALL(lstat, *path, *path)
- FillStatsArray(fields, static_cast<const uv_stat_t*>(SYNC_REQ.ptr));
- } else if (args[1]->IsObject()) {
+ if (args[1]->IsObject()) {
ASYNC_CALL(lstat, args[1], UTF8, *path)
+ } else {
+ SYNC_CALL(lstat, *path, *path)
+ FillStatsArray(env->fs_stats_field_array(),
+ static_cast<const uv_stat_t*>(SYNC_REQ.ptr));
}
}
@@ -709,15 +593,12 @@ static void FStat(const FunctionCallbackInfo<Value>& args) {
int fd = args[0]->Int32Value();
- if (args[1]->IsFloat64Array()) {
- Local<Float64Array> array = args[1].As<Float64Array>();
- CHECK_EQ(array->Length(), 14);
- Local<ArrayBuffer> ab = array->Buffer();
- double* fields = static_cast<double*>(ab->GetContents().Data());
- SYNC_CALL(fstat, 0, fd)
- FillStatsArray(fields, static_cast<const uv_stat_t*>(SYNC_REQ.ptr));
- } else if (args[1]->IsObject()) {
+ if (args[1]->IsObject()) {
ASYNC_CALL(fstat, args[1], UTF8, fd)
+ } else {
+ SYNC_CALL(fstat, nullptr, fd)
+ FillStatsArray(env->fs_stats_field_array(),
+ static_cast<const uv_stat_t*>(SYNC_REQ.ptr));
}
}
@@ -1496,12 +1377,20 @@ static void Mkdtemp(const FunctionCallbackInfo<Value>& args) {
}
}
-void FSInitialize(const FunctionCallbackInfo<Value>& args) {
- Local<Function> stats_constructor = args[0].As<Function>();
- CHECK(stats_constructor->IsFunction());
-
+void GetStatValues(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
- env->set_fs_stats_constructor_function(stats_constructor);
+ double* fields = env->fs_stats_field_array();
+ if (fields == nullptr) {
+ // stat fields contains twice the number of entries because `fs.StatWatcher`
+ // needs room to store data for *two* `fs.Stats` instances.
+ fields = new double[2 * 14];
+ env->set_fs_stats_field_array(fields);
+ }
+ Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(),
+ fields,
+ sizeof(double) * 2 * 14);
+ Local<Float64Array> fields_array = Float64Array::New(ab, 0, 2 * 14);
+ args.GetReturnValue().Set(fields_array);
}
void InitFs(Local<Object> target,
@@ -1510,10 +1399,6 @@ void InitFs(Local<Object> target,
void* priv) {
Environment* env = Environment::GetCurrent(context);
- // Function which creates a new Stats object.
- target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "FSInitialize"),
- env->NewFunctionTemplate(FSInitialize)->GetFunction());
-
env->SetMethod(target, "access", Access);
env->SetMethod(target, "close", Close);
env->SetMethod(target, "open", Open);
@@ -1552,6 +1437,8 @@ void InitFs(Local<Object> target,
env->SetMethod(target, "mkdtemp", Mkdtemp);
+ env->SetMethod(target, "getStatValues", GetStatValues);
+
StatWatcher::Initialize(env, target);
// Create FunctionTemplate for FSReqWrap
diff --git a/src/node_internals.h b/src/node_internals.h
index ff1f1cd11d..0ee694a228 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -162,7 +162,7 @@ NO_RETURN void FatalError(const char* location, const char* message);
void ProcessEmitWarning(Environment* env, const char* fmt, ...);
-v8::Local<v8::Value> BuildStatsObject(Environment* env, const uv_stat_t* s);
+void FillStatsArray(double* fields, const uv_stat_t* s);
void SetupProcessObject(Environment* env,
int argc,
diff --git a/src/node_stat_watcher.cc b/src/node_stat_watcher.cc
index 9289efcf3e..9eeed77476 100644
--- a/src/node_stat_watcher.cc
+++ b/src/node_stat_watcher.cc
@@ -86,12 +86,11 @@ void StatWatcher::Callback(uv_fs_poll_t* handle,
Environment* env = wrap->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
- Local<Value> argv[] = {
- BuildStatsObject(env, curr),
- BuildStatsObject(env, prev),
- Integer::New(env->isolate(), status)
- };
- wrap->MakeCallback(env->onchange_string(), arraysize(argv), argv);
+
+ FillStatsArray(env->fs_stats_field_array(), curr);
+ FillStatsArray(env->fs_stats_field_array() + 14, prev);
+ Local<Value> arg = Integer::New(env->isolate(), status);
+ wrap->MakeCallback(env->onchange_string(), 1, &arg);
}
diff --git a/test/parallel/test-fs-sync-fd-leak.js b/test/parallel/test-fs-sync-fd-leak.js
index 10b09481dd..7e785ea3a2 100644
--- a/test/parallel/test-fs-sync-fd-leak.js
+++ b/test/parallel/test-fs-sync-fd-leak.js
@@ -39,7 +39,7 @@ fs.writeSync = function() {
throw new Error('BAM');
};
-fs.fstatSync = function() {
+process.binding('fs').fstat = function() {
throw new Error('BAM');
};