fs: return stats to JS in sync methods

- Reduce reference to the global `statValues` by returning
  the changed stats array from the synchronous methods. Having
  a local returned value also makes the future integration
  of BigInt easier.
- Also returns the filled array from node::FillGlobalStatsArray
  and node::FillStatsArray in the C++ side.

PR-URL: https://github.com/nodejs/node/pull/20167
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Khaidi Chu <i@2333.moe>
Reviewed-By: Gus Caplan <me@gus.host>
This commit is contained in:
Joyee Cheung 2018-04-07 16:57:22 +08:00
parent 8b012e1464
commit ce4c8c823c
No known key found for this signature in database
GPG Key ID: F586868AAD831D0C
5 changed files with 60 additions and 55 deletions

View File

@ -57,7 +57,6 @@ const {
preprocessSymlinkDestination, preprocessSymlinkDestination,
Stats, Stats,
getStatsFromBinding, getStatsFromBinding,
getStatsFromGlobalBinding,
stringToFlags, stringToFlags,
stringToSymlinkType, stringToSymlinkType,
toUnixTimestamp, toUnixTimestamp,
@ -158,10 +157,10 @@ function isFd(path) {
fs.Stats = Stats; fs.Stats = Stats;
function isFileType(fileType) { function isFileType(stats, fileType) {
// Use stats array directly to avoid creating an fs.Stats instance just for // Use stats array directly to avoid creating an fs.Stats instance just for
// our internal use. // our internal use.
return (statValues[1/* mode */] & S_IFMT) === fileType; return (stats[1/* mode */] & S_IFMT) === fileType;
} }
// Don't allow mode to accidentally be overwritten. // Don't allow mode to accidentally be overwritten.
@ -330,15 +329,15 @@ function readFileAfterOpen(err, fd) {
binding.fstat(fd, req); binding.fstat(fd, req);
} }
function readFileAfterStat(err) { function readFileAfterStat(err, stats) {
var context = this.context; var context = this.context;
if (err) if (err)
return context.close(err); return context.close(err);
var size; var size;
if (isFileType(S_IFREG)) if (isFileType(stats, S_IFREG))
size = context.size = statValues[8]; size = context.size = stats[8];
else else
size = context.size = 0; size = context.size = 0;
@ -411,11 +410,12 @@ function readFileAfterClose(err) {
function tryStatSync(fd, isUserFd) { function tryStatSync(fd, isUserFd) {
const ctx = {}; const ctx = {};
binding.fstat(fd, undefined, ctx); const stats = binding.fstat(fd, undefined, ctx);
if (ctx.errno !== undefined && !isUserFd) { if (ctx.errno !== undefined && !isUserFd) {
fs.closeSync(fd); fs.closeSync(fd);
throw errors.uvException(ctx); throw errors.uvException(ctx);
} }
return stats;
} }
function tryCreateBuffer(size, fd, isUserFd) { function tryCreateBuffer(size, fd, isUserFd) {
@ -450,10 +450,10 @@ fs.readFileSync = function(path, options) {
var isUserFd = isFd(path); // file descriptor ownership var isUserFd = isFd(path); // file descriptor ownership
var fd = isUserFd ? path : fs.openSync(path, options.flag || 'r', 0o666); var fd = isUserFd ? path : fs.openSync(path, options.flag || 'r', 0o666);
tryStatSync(fd, isUserFd); const stats = tryStatSync(fd, isUserFd);
var size; var size;
if (isFileType(S_IFREG)) if (isFileType(stats, S_IFREG))
size = statValues[8]; size = stats[8];
else else
size = 0; size = 0;
var pos = 0; var pos = 0;
@ -890,27 +890,29 @@ fs.stat = function(path, callback) {
fs.fstatSync = function(fd) { fs.fstatSync = function(fd) {
validateUint32(fd, 'fd'); validateUint32(fd, 'fd');
const ctx = { fd }; const ctx = { fd };
binding.fstat(fd, undefined, ctx); const stats = binding.fstat(fd, undefined, ctx);
handleErrorFromBinding(ctx); handleErrorFromBinding(ctx);
return getStatsFromGlobalBinding(); return getStatsFromBinding(stats);
}; };
fs.lstatSync = function(path) { fs.lstatSync = function(path) {
path = getPathFromURL(path); path = getPathFromURL(path);
validatePath(path); validatePath(path);
const ctx = { path }; const ctx = { path };
binding.lstat(pathModule.toNamespacedPath(path), undefined, ctx); const stats = binding.lstat(pathModule.toNamespacedPath(path),
undefined, ctx);
handleErrorFromBinding(ctx); handleErrorFromBinding(ctx);
return getStatsFromGlobalBinding(); return getStatsFromBinding(stats);
}; };
fs.statSync = function(path) { fs.statSync = function(path) {
path = getPathFromURL(path); path = getPathFromURL(path);
validatePath(path); validatePath(path);
const ctx = { path }; const ctx = { path };
binding.stat(pathModule.toNamespacedPath(path), undefined, ctx); const stats = binding.stat(pathModule.toNamespacedPath(path),
undefined, ctx);
handleErrorFromBinding(ctx); handleErrorFromBinding(ctx);
return getStatsFromGlobalBinding(); return getStatsFromBinding(stats);
}; };
fs.readlink = function(path, options, callback) { fs.readlink = function(path, options, callback) {
@ -1439,7 +1441,7 @@ function StatWatcher() {
this._handle.onchange = function(newStatus, stats) { this._handle.onchange = function(newStatus, stats) {
if (oldStatus === -1 && if (oldStatus === -1 &&
newStatus === -1 && newStatus === -1 &&
statValues[2/* new nlink */] === statValues[16/* old nlink */]) return; stats[2/* new nlink */] === stats[16/* old nlink */]) return;
oldStatus = newStatus; oldStatus = newStatus;
self.emit('change', getStatsFromBinding(stats), self.emit('change', getStatsFromBinding(stats),
@ -1666,7 +1668,8 @@ fs.realpathSync = function realpathSync(p, options) {
// continue if not a symlink, break if a pipe/socket // continue if not a symlink, break if a pipe/socket
if (knownHard[base] || (cache && cache.get(base) === base)) { if (knownHard[base] || (cache && cache.get(base) === base)) {
if (isFileType(S_IFIFO) || isFileType(S_IFSOCK)) { if (isFileType(statValues, S_IFIFO) ||
isFileType(statValues, S_IFSOCK)) {
break; break;
} }
continue; continue;
@ -1682,10 +1685,10 @@ fs.realpathSync = function realpathSync(p, options) {
var baseLong = pathModule.toNamespacedPath(base); var baseLong = pathModule.toNamespacedPath(base);
const ctx = { path: base }; const ctx = { path: base };
binding.lstat(baseLong, undefined, ctx); const stats = binding.lstat(baseLong, undefined, ctx);
handleErrorFromBinding(ctx); handleErrorFromBinding(ctx);
if (!isFileType(S_IFLNK)) { if (!isFileType(stats, S_IFLNK)) {
knownHard[base] = true; knownHard[base] = true;
if (cache) cache.set(base, base); if (cache) cache.set(base, base);
continue; continue;
@ -1696,8 +1699,8 @@ fs.realpathSync = function realpathSync(p, options) {
var linkTarget = null; var linkTarget = null;
var id; var id;
if (!isWindows) { if (!isWindows) {
var dev = statValues[0].toString(32); var dev = stats[0].toString(32);
var ino = statValues[7].toString(32); var ino = stats[7].toString(32);
id = `${dev}:${ino}`; id = `${dev}:${ino}`;
if (seenLinks[id]) { if (seenLinks[id]) {
linkTarget = seenLinks[id]; linkTarget = seenLinks[id];
@ -1778,7 +1781,7 @@ fs.realpath = function realpath(p, options, callback) {
// On windows, check that the root exists. On unix there is no need. // On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) { if (isWindows && !knownHard[base]) {
fs.lstat(base, function(err) { fs.lstat(base, function(err, stats) {
if (err) return callback(err); if (err) return callback(err);
knownHard[base] = true; knownHard[base] = true;
LOOP(); LOOP();
@ -1811,7 +1814,8 @@ fs.realpath = function realpath(p, options, callback) {
// continue if not a symlink, break if a pipe/socket // continue if not a symlink, break if a pipe/socket
if (knownHard[base]) { if (knownHard[base]) {
if (isFileType(S_IFIFO) || isFileType(S_IFSOCK)) { if (isFileType(statValues, S_IFIFO) ||
isFileType(statValues, S_IFSOCK)) {
return callback(null, encodeRealpathResult(p, options)); return callback(null, encodeRealpathResult(p, options));
} }
return process.nextTick(LOOP); return process.nextTick(LOOP);
@ -1820,14 +1824,11 @@ fs.realpath = function realpath(p, options, callback) {
return fs.lstat(base, gotStat); return fs.lstat(base, gotStat);
} }
function gotStat(err) { function gotStat(err, stats) {
if (err) return callback(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 not a symlink, skip to the next path part
if (!isFileType(S_IFLNK)) { if (!stats.isSymbolicLink()) {
knownHard[base] = true; knownHard[base] = true;
return process.nextTick(LOOP); return process.nextTick(LOOP);
} }
@ -1837,8 +1838,8 @@ fs.realpath = function realpath(p, options, callback) {
// dev/ino always return 0 on windows, so skip the check. // dev/ino always return 0 on windows, so skip the check.
let id; let id;
if (!isWindows) { if (!isWindows) {
var dev = statValues[0].toString(32); var dev = stats.dev.toString(32);
var ino = statValues[7].toString(32); var ino = stats.ino.toString(32);
id = `${dev}:${ino}`; id = `${dev}:${ino}`;
if (seenLinks[id]) { if (seenLinks[id]) {
return gotTarget(null, seenLinks[id], base); return gotTarget(null, seenLinks[id], base);

View File

@ -35,7 +35,6 @@ const {
UV_FS_SYMLINK_DIR, UV_FS_SYMLINK_DIR,
UV_FS_SYMLINK_JUNCTION UV_FS_SYMLINK_JUNCTION
} = process.binding('constants').fs; } = process.binding('constants').fs;
const { statValues } = process.binding('fs');
const isWindows = process.platform === 'win32'; const isWindows = process.platform === 'win32';
@ -217,10 +216,6 @@ function getStatsFromBinding(stats, offset = 0) {
stats[12 + offset], stats[13 + offset]); stats[12 + offset], stats[13 + offset]);
} }
function getStatsFromGlobalBinding(offset = 0) {
return getStatsFromBinding(statValues, offset);
}
function stringToFlags(flags) { function stringToFlags(flags) {
if (typeof flags === 'number') { if (typeof flags === 'number') {
return flags; return flags;
@ -442,7 +437,6 @@ module.exports = {
preprocessSymlinkDestination, preprocessSymlinkDestination,
realpathCacheKey: Symbol('realpathCacheKey'), realpathCacheKey: Symbol('realpathCacheKey'),
getStatsFromBinding, getStatsFromBinding,
getStatsFromGlobalBinding,
stringToFlags, stringToFlags,
stringToSymlinkType, stringToSymlinkType,
Stats, Stats,

View File

@ -410,8 +410,7 @@ void FSReqWrap::Reject(Local<Value> reject) {
} }
void FSReqWrap::ResolveStat(const uv_stat_t* stat) { void FSReqWrap::ResolveStat(const uv_stat_t* stat) {
node::FillGlobalStatsArray(env(), stat); Resolve(node::FillGlobalStatsArray(env(), stat));
Resolve(env()->fs_stats_field_array()->GetJSArray());
} }
void FSReqWrap::Resolve(Local<Value> value) { void FSReqWrap::Resolve(Local<Value> value) {
@ -832,10 +831,13 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
FS_SYNC_TRACE_BEGIN(stat); FS_SYNC_TRACE_BEGIN(stat);
int err = SyncCall(env, args[2], &req_wrap_sync, "stat", uv_fs_stat, *path); int err = SyncCall(env, args[2], &req_wrap_sync, "stat", uv_fs_stat, *path);
FS_SYNC_TRACE_END(stat); FS_SYNC_TRACE_END(stat);
if (err == 0) { if (err != 0) {
node::FillGlobalStatsArray(env, return; // error info is in ctx
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
} }
Local<Value> arr = node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
args.GetReturnValue().Set(arr);
} }
} }
@ -859,10 +861,13 @@ static void LStat(const FunctionCallbackInfo<Value>& args) {
int err = SyncCall(env, args[2], &req_wrap_sync, "lstat", uv_fs_lstat, int err = SyncCall(env, args[2], &req_wrap_sync, "lstat", uv_fs_lstat,
*path); *path);
FS_SYNC_TRACE_END(lstat); FS_SYNC_TRACE_END(lstat);
if (err == 0) { if (err != 0) {
node::FillGlobalStatsArray(env, return; // error info is in ctx
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
} }
Local<Value> arr = node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
args.GetReturnValue().Set(arr);
} }
} }
@ -885,10 +890,13 @@ static void FStat(const FunctionCallbackInfo<Value>& args) {
FS_SYNC_TRACE_BEGIN(fstat); FS_SYNC_TRACE_BEGIN(fstat);
int err = SyncCall(env, args[2], &req_wrap_sync, "fstat", uv_fs_fstat, fd); int err = SyncCall(env, args[2], &req_wrap_sync, "fstat", uv_fs_fstat, fd);
FS_SYNC_TRACE_END(fstat); FS_SYNC_TRACE_END(fstat);
if (err == 0) { if (err != 0) {
node::FillGlobalStatsArray(env, return; // error info is in ctx
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
} }
Local<Value> arr = node::FillGlobalStatsArray(env,
static_cast<const uv_stat_t*>(req_wrap_sync.req.ptr));
args.GetReturnValue().Set(arr);
} }
} }

View File

@ -313,7 +313,7 @@ v8::Maybe<bool> ProcessEmitDeprecationWarning(Environment* env,
const char* deprecation_code); const char* deprecation_code);
template <typename NativeT, typename V8T> template <typename NativeT, typename V8T>
void FillStatsArray(AliasedBuffer<NativeT, V8T>* fields_ptr, v8::Local<v8::Value> FillStatsArray(AliasedBuffer<NativeT, V8T>* fields_ptr,
const uv_stat_t* s, int offset = 0) { const uv_stat_t* s, int offset = 0) {
AliasedBuffer<NativeT, V8T>& fields = *fields_ptr; AliasedBuffer<NativeT, V8T>& fields = *fields_ptr;
fields[offset + 0] = s->st_dev; fields[offset + 0] = s->st_dev;
@ -347,12 +347,14 @@ void FillStatsArray(AliasedBuffer<NativeT, V8T>* fields_ptr,
X(12, ctim) X(12, ctim)
X(13, birthtim) X(13, birthtim)
#undef X #undef X
return fields_ptr->GetJSArray();
} }
inline void FillGlobalStatsArray(Environment* env, inline v8::Local<v8::Value> FillGlobalStatsArray(Environment* env,
const uv_stat_t* s, const uv_stat_t* s,
int offset = 0) { int offset = 0) {
node::FillStatsArray(env->fs_stats_field_array(), s, offset); return node::FillStatsArray(env->fs_stats_field_array(), s, offset);
} }
void SetupProcessObject(Environment* env, void SetupProcessObject(Environment* env,

View File

@ -108,12 +108,12 @@ void StatWatcher::Callback(uv_fs_poll_t* handle,
HandleScope handle_scope(env->isolate()); HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context()); Context::Scope context_scope(env->context());
node::FillGlobalStatsArray(env, curr); Local<Value> arr = node::FillGlobalStatsArray(env, curr);
node::FillGlobalStatsArray(env, prev, env->kFsStatsFieldsLength); node::FillGlobalStatsArray(env, prev, env->kFsStatsFieldsLength);
Local<Value> argv[2] { Local<Value> argv[2] {
Integer::New(env->isolate(), status), Integer::New(env->isolate(), status),
env->fs_stats_field_array()->GetJSArray() arr
}; };
wrap->MakeCallback(env->onchange_string(), arraysize(argv), argv); wrap->MakeCallback(env->onchange_string(), arraysize(argv), argv);
} }