fs: add *timeNs properties to BigInt Stats objects · nodejs/node@d36adcd · GitHub
Skip to content

Commit d36adcd

Browse files
joyeecheungBridgeAR
authored andcommitted
fs: add *timeNs properties to BigInt Stats objects
- Extend the aliased buffer for stats objects to contain the entire time spec (seconds and nanoseconds) for the time values instead of calculating the milliseconds in C++ and lose precision there. - Calculate the nanosecond-precision time values in JS and expose them in BigInt Stats objects as `*timeNs`. The millisecond-precision values are now calculated from the nanosecond-precision values. PR-URL: #21387 Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Gus Caplan <me@gus.host>
1 parent 5a7154e commit d36adcd

7 files changed

Lines changed: 275 additions & 129 deletions

File tree

doc/api/fs.md

Lines changed: 67 additions & 4 deletions

lib/internal/fs/utils.js

Lines changed: 108 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const { Reflect } = primordials;
3+
const { Object, Reflect } = primordials;
44

55
const { Buffer, kMaxLength } = require('buffer');
66
const {
@@ -16,7 +16,8 @@ const {
1616
} = require('internal/errors');
1717
const {
1818
isUint8Array,
19-
isDate
19+
isDate,
20+
isBigUint64Array
2021
} = require('internal/util/types');
2122
const { once } = require('internal/util');
2223
const { toPathIfFileURL } = require('internal/url');
@@ -230,27 +231,9 @@ function preprocessSymlinkDestination(path, type, linkPath) {
230231
}
231232
}
232233

233-
function dateFromNumeric(num) {
234-
return new Date(Number(num) + 0.5);
235-
}
236-
237234
// Constructor for file stats.
238-
function Stats(
239-
dev,
240-
mode,
241-
nlink,
242-
uid,
243-
gid,
244-
rdev,
245-
blksize,
246-
ino,
247-
size,
248-
blocks,
249-
atim_msec,
250-
mtim_msec,
251-
ctim_msec,
252-
birthtim_msec
253-
) {
235+
function StatsBase(dev, mode, nlink, uid, gid, rdev, blksize,
236+
ino, size, blocks) {
254237
this.dev = dev;
255238
this.mode = mode;
256239
this.nlink = nlink;
@@ -261,63 +244,132 @@ function Stats(
261244
this.ino = ino;
262245
this.size = size;
263246
this.blocks = blocks;
264-
this.atimeMs = atim_msec;
265-
this.mtimeMs = mtim_msec;
266-
this.ctimeMs = ctim_msec;
267-
this.birthtimeMs = birthtim_msec;
268-
this.atime = dateFromNumeric(atim_msec);
269-
this.mtime = dateFromNumeric(mtim_msec);
270-
this.ctime = dateFromNumeric(ctim_msec);
271-
this.birthtime = dateFromNumeric(birthtim_msec);
272247
}
273248

274-
Stats.prototype._checkModeProperty = function(property) {
275-
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
276-
property === S_IFSOCK)) {
277-
return false; // Some types are not available on Windows
278-
}
279-
if (typeof this.mode === 'bigint') { // eslint-disable-line valid-typeof
280-
return (this.mode & BigInt(S_IFMT)) === BigInt(property);
281-
}
282-
return (this.mode & S_IFMT) === property;
283-
};
284-
285-
Stats.prototype.isDirectory = function() {
249+
StatsBase.prototype.isDirectory = function() {
286250
return this._checkModeProperty(S_IFDIR);
287251
};
288252

289-
Stats.prototype.isFile = function() {
253+
StatsBase.prototype.isFile = function() {
290254
return this._checkModeProperty(S_IFREG);
291255
};
292256

293-
Stats.prototype.isBlockDevice = function() {
257+
StatsBase.prototype.isBlockDevice = function() {
294258
return this._checkModeProperty(S_IFBLK);
295259
};
296260

297-
Stats.prototype.isCharacterDevice = function() {
261+
StatsBase.prototype.isCharacterDevice = function() {
298262
return this._checkModeProperty(S_IFCHR);
299263
};
300264

301-
Stats.prototype.isSymbolicLink = function() {
265+
StatsBase.prototype.isSymbolicLink = function() {
302266
return this._checkModeProperty(S_IFLNK);
303267
};
304268

305-
Stats.prototype.isFIFO = function() {
269+
StatsBase.prototype.isFIFO = function() {
306270
return this._checkModeProperty(S_IFIFO);
307271
};
308272

309-
Stats.prototype.isSocket = function() {
273+
StatsBase.prototype.isSocket = function() {
310274
return this._checkModeProperty(S_IFSOCK);
311275
};
312276

277+
const kNsPerMsBigInt = 10n ** 6n;
278+
const kNsPerSecBigInt = 10n ** 9n;
279+
const kMsPerSec = 10 ** 3;
280+
const kNsPerMs = 10 ** 6;
281+
function msFromTimeSpec(sec, nsec) {
282+
return sec * kMsPerSec + nsec / kNsPerMs;
283+
}
284+
285+
function nsFromTimeSpecBigInt(sec, nsec) {
286+
return sec * kNsPerSecBigInt + nsec;
287+
}
288+
289+
function dateFromMs(ms) {
290+
return new Date(Number(ms) + 0.5);
291+
}
292+
293+
function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize,
294+
ino, size, blocks,
295+
atimeNs, mtimeNs, ctimeNs, birthtimeNs) {
296+
StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize,
297+
ino, size, blocks);
298+
299+
this.atimeMs = atimeNs / kNsPerMsBigInt;
300+
this.mtimeMs = mtimeNs / kNsPerMsBigInt;
301+
this.ctimeMs = ctimeNs / kNsPerMsBigInt;
302+
this.birthtimeMs = birthtimeNs / kNsPerMsBigInt;
303+
this.atimeNs = atimeNs;
304+
this.mtimeNs = mtimeNs;
305+
this.ctimeNs = ctimeNs;
306+
this.birthtimeNs = birthtimeNs;
307+
this.atime = dateFromMs(this.atimeMs);
308+
this.mtime = dateFromMs(this.mtimeMs);
309+
this.ctime = dateFromMs(this.ctimeMs);
310+
this.birthtime = dateFromMs(this.birthtimeMs);
311+
}
312+
313+
Object.setPrototypeOf(BigIntStats.prototype, StatsBase.prototype);
314+
Object.setPrototypeOf(BigIntStats, StatsBase);
315+
316+
BigIntStats.prototype._checkModeProperty = function(property) {
317+
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
318+
property === S_IFSOCK)) {
319+
return false; // Some types are not available on Windows
320+
}
321+
return (this.mode & BigInt(S_IFMT)) === BigInt(property);
322+
};
323+
324+
function Stats(dev, mode, nlink, uid, gid, rdev, blksize,
325+
ino, size, blocks,
326+
atimeMs, mtimeMs, ctimeMs, birthtimeMs) {
327+
StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize,
328+
ino, size, blocks);
329+
this.atimeMs = atimeMs;
330+
this.mtimeMs = mtimeMs;
331+
this.ctimeMs = ctimeMs;
332+
this.birthtimeMs = birthtimeMs;
333+
this.atime = dateFromMs(atimeMs);
334+
this.mtime = dateFromMs(mtimeMs);
335+
this.ctime = dateFromMs(ctimeMs);
336+
this.birthtime = dateFromMs(birthtimeMs);
337+
}
338+
339+
Object.setPrototypeOf(Stats.prototype, StatsBase.prototype);
340+
Object.setPrototypeOf(Stats, StatsBase);
341+
342+
Stats.prototype._checkModeProperty = function(property) {
343+
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
344+
property === S_IFSOCK)) {
345+
return false; // Some types are not available on Windows
346+
}
347+
return (this.mode & S_IFMT) === property;
348+
};
349+
313350
function getStatsFromBinding(stats, offset = 0) {
314-
return new Stats(stats[0 + offset], stats[1 + offset], stats[2 + offset],
315-
stats[3 + offset], stats[4 + offset], stats[5 + offset],
316-
stats[6 + offset], // blksize
317-
stats[7 + offset], stats[8 + offset],
318-
stats[9 + offset], // blocks
319-
stats[10 + offset], stats[11 + offset],
320-
stats[12 + offset], stats[13 + offset]);
351+
if (isBigUint64Array(stats)) {
352+
return new BigIntStats(
353+
stats[0 + offset], stats[1 + offset], stats[2 + offset],
354+
stats[3 + offset], stats[4 + offset], stats[5 + offset],
355+
stats[6 + offset], stats[7 + offset], stats[8 + offset],
356+
stats[9 + offset],
357+
nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]),
358+
nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]),
359+
nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]),
360+
nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset])
361+
);
362+
}
363+
return new Stats(
364+
stats[0 + offset], stats[1 + offset], stats[2 + offset],
365+
stats[3 + offset], stats[4 + offset], stats[5 + offset],
366+
stats[6 + offset], stats[7 + offset], stats[8 + offset],
367+
stats[9 + offset],
368+
msFromTimeSpec(stats[10 + offset], stats[11 + offset]),
369+
msFromTimeSpec(stats[12 + offset], stats[13 + offset]),
370+
msFromTimeSpec(stats[14 + offset], stats[15 + offset]),
371+
msFromTimeSpec(stats[16 + offset], stats[17 + offset])
372+
);
321373
}
322374

323375
function stringToFlags(flags) {
@@ -453,6 +505,7 @@ function warnOnNonPortableTemplate(template) {
453505

454506
module.exports = {
455507
assertEncoding,
508+
BigIntStats, // for testing
456509
copyObject,
457510
Dirent,
458511
getDirents,

src/env.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,32 @@ struct PackageConfig {
102102
};
103103
} // namespace loader
104104

105+
enum class FsStatsOffset {
106+
kDev = 0,
107+
kMode,
108+
kNlink,
109+
kUid,
110+
kGid,
111+
kRdev,
112+
kBlkSize,
113+
kIno,
114+
kSize,
115+
kBlocks,
116+
kATimeSec,
117+
kATimeNsec,
118+
kMTimeSec,
119+
kMTimeNsec,
120+
kCTimeSec,
121+
kCTimeNsec,
122+
kBirthTimeSec,
123+
kBirthTimeNsec,
124+
kFsStatsFieldsNumber
125+
};
126+
105127
// Stat fields buffers contain twice the number of entries in an uv_stat_t
106128
// because `fs.StatWatcher` needs room to store 2 `fs.Stats` instances.
107-
constexpr size_t kFsStatsFieldsNumber = 14;
108-
constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
129+
constexpr size_t kFsStatsBufferLength =
130+
static_cast<size_t>(FsStatsOffset::kFsStatsFieldsNumber) * 2;
109131

110132
// PER_ISOLATE_* macros: We have a lot of per-isolate properties
111133
// and adding and maintaining their getters and setters by hand would be

src/node_file.cc

Lines changed: 7 additions & 4 deletions

0 commit comments

Comments
 (0)