GeezerVison is showing github.com — fs: improve error perf of sync `lstat`+`fstat` · nodejs/node@05e25e0
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: benchmark/fs/bench-statSync-failure.js +15-7Lines changed: 15 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -5,21 +5,29 @@ const fs = require('fs'); | 5 | 5 | const path = require('path'); | 6 | 6 |
| 7 | 7 | const bench = common.createBenchmark(main, { | 8 | | - n: [1e6], | 9 | | - statSyncType: ['throw', 'noThrow'], | | 8 | + n: [1e4], | | 9 | + statSyncType: ['fstatSync', 'lstatSync', 'statSync'], | | 10 | + throwType: [ 'throw', 'noThrow' ], | | 11 | +}, { | | 12 | + // fstatSync does not support throwIfNoEntry | | 13 | + combinationFilter: ({ statSyncType, throwType }) => !(statSyncType === 'fstatSync' && throwType === 'noThrow'), | 10 | 14 | }); | 11 | 15 |
| 12 | 16 |
| 13 | | -function main({ n, statSyncType }) { | 14 | | - const arg = path.join(__dirname, 'non.existent'); | | 17 | +function main({ n, statSyncType, throwType }) { | | 18 | + const arg = (statSyncType === 'fstatSync' ? | | 19 | + (1 << 30) : | | 20 | + path.join(__dirname, 'non.existent')); | | 21 | + | | 22 | + const fn = fs[statSyncType]; | 15 | 23 |
| 16 | 24 | bench.start(); | 17 | 25 | for (let i = 0; i < n; i++) { | 18 | | - if (statSyncType === 'noThrow') { | 19 | | - fs.statSync(arg, { throwIfNoEntry: false }); | | 26 | + if (throwType === 'noThrow') { | | 27 | + fn(arg, { throwIfNoEntry: false }); | 20 | 28 | } else { | 21 | 29 | try { | 22 | | - fs.statSync(arg); | | 30 | + fn(arg); | 23 | 31 | } catch { | 24 | 32 | // Continue regardless of error. | 25 | 33 | } | |
Collapse file Copy file name to clipboardExpand all lines: benchmark/fs/bench-statSync.js +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -4,7 +4,7 @@ const common = require('../common'); | 4 | 4 | const fs = require('fs'); | 5 | 5 |
| 6 | 6 | const bench = common.createBenchmark(main, { | 7 | | - n: [1e6], | | 7 | + n: [1e4], | 8 | 8 | statSyncType: ['fstatSync', 'lstatSync', 'statSync'], | 9 | 9 | }); | 10 | 10 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/fs.js +29-39Lines changed: 29 additions & 39 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -74,8 +74,6 @@ const { | 74 | 74 | ERR_INVALID_ARG_VALUE, | 75 | 75 | }, | 76 | 76 | AbortError, | 77 | | - uvErrmapGet, | 78 | | - uvException, | 79 | 77 | } = require('internal/errors'); | 80 | 78 |
| 81 | 79 | const { | @@ -402,11 +400,9 @@ function readFile(path, options, callback) { | 402 | 400 | } | 403 | 401 |
| 404 | 402 | function tryStatSync(fd, isUserFd) { | 405 | | - const ctx = {}; | 406 | | - const stats = binding.fstat(fd, false, undefined, ctx); | 407 | | - if (ctx.errno !== undefined && !isUserFd) { | | 403 | + const stats = binding.fstat(fd, false, undefined, true /* shouldNotThrow */); | | 404 | + if (stats === undefined && !isUserFd) { | 408 | 405 | fs.closeSync(fd); | 409 | | - throw uvException(ctx); | 410 | 406 | } | 411 | 407 | return stats; | 412 | 408 | } | @@ -1614,33 +1610,21 @@ function statfs(path, options = { bigint: false }, callback) { | 1614 | 1610 | binding.statfs(pathModule.toNamespacedPath(path), options.bigint, req); | 1615 | 1611 | } | 1616 | 1612 |
| 1617 | | -function hasNoEntryError(ctx) { | 1618 | | - if (ctx.errno) { | 1619 | | - const uvErr = uvErrmapGet(ctx.errno); | 1620 | | - return uvErr?.[0] === 'ENOENT'; | 1621 | | - } | 1622 | | - | 1623 | | - if (ctx.error) { | 1624 | | - return ctx.error.code === 'ENOENT'; | 1625 | | - } | 1626 | | - | 1627 | | - return false; | 1628 | | -} | 1629 | | - | 1630 | 1613 | /** | 1631 | 1614 | * Synchronously retrieves the `fs.Stats` for | 1632 | 1615 | * the file descriptor. | 1633 | 1616 | * @param {number} fd | 1634 | 1617 | * @param {{ | 1635 | 1618 | * bigint?: boolean; | 1636 | 1619 | * }} [options] | 1637 | | - * @returns {Stats} | | 1620 | + * @returns {Stats | undefined} | 1638 | 1621 | */ | 1639 | 1622 | function fstatSync(fd, options = { bigint: false }) { | 1640 | 1623 | fd = getValidatedFd(fd); | 1641 | | - const ctx = { fd }; | 1642 | | - const stats = binding.fstat(fd, options.bigint, undefined, ctx); | 1643 | | - handleErrorFromBinding(ctx); | | 1624 | + const stats = binding.fstat(fd, options.bigint, undefined, false); | | 1625 | + if (stats === undefined) { | | 1626 | + return; | | 1627 | + } | 1644 | 1628 | return getStatsFromBinding(stats); | 1645 | 1629 | } | 1646 | 1630 |
| @@ -1652,17 +1636,20 @@ function fstatSync(fd, options = { bigint: false }) { | 1652 | 1636 | * bigint?: boolean; | 1653 | 1637 | * throwIfNoEntry?: boolean; | 1654 | 1638 | * }} [options] | 1655 | | - * @returns {Stats} | | 1639 | + * @returns {Stats | undefined} | 1656 | 1640 | */ | 1657 | 1641 | function lstatSync(path, options = { bigint: false, throwIfNoEntry: true }) { | 1658 | 1642 | path = getValidatedPath(path); | 1659 | | - const ctx = { path }; | 1660 | | - const stats = binding.lstat(pathModule.toNamespacedPath(path), | 1661 | | - options.bigint, undefined, ctx); | 1662 | | - if (options.throwIfNoEntry === false && hasNoEntryError(ctx)) { | 1663 | | - return undefined; | | 1643 | + const stats = binding.lstat( | | 1644 | + pathModule.toNamespacedPath(path), | | 1645 | + options.bigint, | | 1646 | + undefined, | | 1647 | + options.throwIfNoEntry, | | 1648 | + ); | | 1649 | + | | 1650 | + if (stats === undefined) { | | 1651 | + return; | 1664 | 1652 | } | 1665 | | - handleErrorFromBinding(ctx); | 1666 | 1653 | return getStatsFromBinding(stats); | 1667 | 1654 | } | 1668 | 1655 |
| @@ -2665,9 +2652,10 @@ function realpathSync(p, options) { | 2665 | 2652 |
| 2666 | 2653 | // On windows, check that the root exists. On unix there is no need. | 2667 | 2654 | if (isWindows) { | 2668 | | - const ctx = { path: base }; | 2669 | | - binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx); | 2670 | | - handleErrorFromBinding(ctx); | | 2655 | + const out = binding.lstat(pathModule.toNamespacedPath(base), false, undefined, true /* throwIfNoEntry */); | | 2656 | + if (out === undefined) { | | 2657 | + return; | | 2658 | + } | 2671 | 2659 | knownHard.add(base); | 2672 | 2660 | } | 2673 | 2661 |
| @@ -2707,9 +2695,10 @@ function realpathSync(p, options) { | 2707 | 2695 | // for our internal use. | 2708 | 2696 |
| 2709 | 2697 | const baseLong = pathModule.toNamespacedPath(base); | 2710 | | - const ctx = { path: base }; | 2711 | | - const stats = binding.lstat(baseLong, true, undefined, ctx); | 2712 | | - handleErrorFromBinding(ctx); | | 2698 | + const stats = binding.lstat(baseLong, true, undefined, true /* throwIfNoEntry */); | | 2699 | + if (stats === undefined) { | | 2700 | + return; | | 2701 | + } | 2713 | 2702 |
| 2714 | 2703 | if (!isFileType(stats, S_IFLNK)) { | 2715 | 2704 | knownHard.add(base); | @@ -2748,9 +2737,10 @@ function realpathSync(p, options) { | 2748 | 2737 |
| 2749 | 2738 | // On windows, check that the root exists. On unix there is no need. | 2750 | 2739 | if (isWindows && !knownHard.has(base)) { | 2751 | | - const ctx = { path: base }; | 2752 | | - binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx); | 2753 | | - handleErrorFromBinding(ctx); | | 2740 | + const out = binding.lstat(pathModule.toNamespacedPath(base), false, undefined, true /* throwIfNoEntry */); | | 2741 | + if (out === undefined) { | | 2742 | + return; | | 2743 | + } | 2754 | 2744 | knownHard.add(base); | 2755 | 2745 | } | 2756 | 2746 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/node_file.cc +26-17Lines changed: 26 additions & 17 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1215,21 +1215,26 @@ static void LStat(const FunctionCallbackInfo<Value>& args) { | 1215 | 1215 | CHECK_NOT_NULL(*path); | 1216 | 1216 |
| 1217 | 1217 | bool use_bigint = args[1]->IsTrue(); | 1218 | | - FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint); | 1219 | | - if (req_wrap_async != nullptr) { // lstat(path, use_bigint, req) | | 1218 | + if (!args[2]->IsUndefined()) { // lstat(path, use_bigint, req) | | 1219 | + FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint); | 1220 | 1220 | FS_ASYNC_TRACE_BEGIN1( | 1221 | 1221 | UV_FS_LSTAT, req_wrap_async, "path", TRACE_STR_COPY(*path)) | 1222 | 1222 | AsyncCall(env, req_wrap_async, args, "lstat", UTF8, AfterStat, | 1223 | 1223 | uv_fs_lstat, *path); | 1224 | | - } else { // lstat(path, use_bigint, undefined, ctx) | 1225 | | - CHECK_EQ(argc, 4); | 1226 | | - FSReqWrapSync req_wrap_sync; | | 1224 | + } else { // lstat(path, use_bigint, undefined, throw_if_no_entry) | | 1225 | + bool do_not_throw_if_no_entry = args[3]->IsFalse(); | | 1226 | + FSReqWrapSync req_wrap_sync("lstat", *path); | 1227 | 1227 | FS_SYNC_TRACE_BEGIN(lstat); | 1228 | | - int err = SyncCall(env, args[3], &req_wrap_sync, "lstat", uv_fs_lstat, | 1229 | | - *path); | | 1228 | + int result; | | 1229 | + if (do_not_throw_if_no_entry) { | | 1230 | + result = SyncCallAndThrowIf( | | 1231 | + is_uv_error_except_no_entry, env, &req_wrap_sync, uv_fs_lstat, *path); | | 1232 | + } else { | | 1233 | + result = SyncCallAndThrowOnError(env, &req_wrap_sync, uv_fs_lstat, *path); | | 1234 | + } | 1230 | 1235 | FS_SYNC_TRACE_END(lstat); | 1231 | | - if (err != 0) { | 1232 | | - return; // error info is in ctx | | 1236 | + if (is_uv_error(result)) { | | 1237 | + return; | 1233 | 1238 | } | 1234 | 1239 |
| 1235 | 1240 | Local<Value> arr = FillGlobalStatsArray(binding_data, use_bigint, | @@ -1250,19 +1255,23 @@ static void FStat(const FunctionCallbackInfo<Value>& args) { | 1250 | 1255 | int fd = args[0].As<Int32>()->Value(); | 1251 | 1256 |
| 1252 | 1257 | bool use_bigint = args[1]->IsTrue(); | 1253 | | - FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint); | 1254 | | - if (req_wrap_async != nullptr) { // fstat(fd, use_bigint, req) | | 1258 | + if (!args[2]->IsUndefined()) { // fstat(fd, use_bigint, req) | | 1259 | + FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint); | 1255 | 1260 | FS_ASYNC_TRACE_BEGIN0(UV_FS_FSTAT, req_wrap_async) | 1256 | 1261 | AsyncCall(env, req_wrap_async, args, "fstat", UTF8, AfterStat, | 1257 | 1262 | uv_fs_fstat, fd); | 1258 | | - } else { // fstat(fd, use_bigint, undefined, ctx) | 1259 | | - CHECK_EQ(argc, 4); | 1260 | | - FSReqWrapSync req_wrap_sync; | | 1263 | + } else { // fstat(fd, use_bigint, undefined, do_not_throw_error) | | 1264 | + bool do_not_throw_error = args[2]->IsTrue(); | | 1265 | + const auto should_throw = [do_not_throw_error](int result) { | | 1266 | + return is_uv_error(result) && !do_not_throw_error; | | 1267 | + }; | | 1268 | + FSReqWrapSync req_wrap_sync("fstat"); | 1261 | 1269 | FS_SYNC_TRACE_BEGIN(fstat); | 1262 | | - int err = SyncCall(env, args[3], &req_wrap_sync, "fstat", uv_fs_fstat, fd); | | 1270 | + int err = | | 1271 | + SyncCallAndThrowIf(should_throw, env, &req_wrap_sync, uv_fs_fstat, fd); | 1263 | 1272 | FS_SYNC_TRACE_END(fstat); | 1264 | | - if (err != 0) { | 1265 | | - return; // error info is in ctx | | 1273 | + if (is_uv_error(err)) { | | 1274 | + return; | 1266 | 1275 | } | 1267 | 1276 |
| 1268 | 1277 | Local<Value> arr = FillGlobalStatsArray(binding_data, use_bigint, | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-fs-sync-fd-leak.js +2-7Lines changed: 2 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -25,7 +25,6 @@ require('../common'); | 25 | 25 | const assert = require('assert'); | 26 | 26 | const fs = require('fs'); | 27 | 27 | const { internalBinding } = require('internal/test/binding'); | 28 | | -const { UV_EBADF } = internalBinding('uv'); | 29 | 28 |
| 30 | 29 | // Ensure that (read|write|append)FileSync() closes the file descriptor | 31 | 30 | fs.openSync = function() { | @@ -42,15 +41,11 @@ fs.writeSync = function() { | 42 | 41 | throw new Error('BAM'); | 43 | 42 | }; | 44 | 43 |
| 45 | | -internalBinding('fs').fstat = function(fd, bigint, _, ctx) { | 46 | | - ctx.errno = UV_EBADF; | 47 | | - ctx.syscall = 'fstat'; | | 44 | +internalBinding('fs').fstat = function() { | | 45 | + throw new Error('EBADF: bad file descriptor, fstat'); | 48 | 46 | }; | 49 | 47 |
| 50 | 48 | let close_called = 0; | 51 | | -ensureThrows(function() { | 52 | | - fs.readFileSync('dummy'); | 53 | | -}, 'EBADF: bad file descriptor, fstat'); | 54 | 49 | ensureThrows(function() { | 55 | 50 | fs.writeFileSync('dummy', 'xxx'); | 56 | 51 | }, 'BAM'); | |
Collapse file Copy file name to clipboardExpand all lines: typings/internalBinding/fs.d.ts +6-6Lines changed: 6 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -92,9 +92,9 @@ declare namespace InternalFSBinding { | 92 | 92 | function fstat(fd: number, useBigint: boolean, req: FSReqCallback<Float64Array | BigUint64Array>): void; | 93 | 93 | function fstat(fd: number, useBigint: true, req: FSReqCallback<BigUint64Array>): void; | 94 | 94 | function fstat(fd: number, useBigint: false, req: FSReqCallback<Float64Array>): void; | 95 | | - function fstat(fd: number, useBigint: boolean, req: undefined, ctx: FSSyncContext): Float64Array | BigUint64Array; | 96 | | - function fstat(fd: number, useBigint: true, req: undefined, ctx: FSSyncContext): BigUint64Array; | 97 | | - function fstat(fd: number, useBigint: false, req: undefined, ctx: FSSyncContext): Float64Array; | | 95 | + function fstat(fd: number, useBigint: boolean, req: undefined, shouldNotThrow: boolean): Float64Array | BigUint64Array; | | 96 | + function fstat(fd: number, useBigint: true, req: undefined, shouldNotThrow: boolean): BigUint64Array; | | 97 | + function fstat(fd: number, useBigint: false, req: undefined, shouldNotThrow: boolean): Float64Array; | 98 | 98 | function fstat(fd: number, useBigint: boolean, usePromises: typeof kUsePromises): Promise<Float64Array | BigUint64Array>; | 99 | 99 | function fstat(fd: number, useBigint: true, usePromises: typeof kUsePromises): Promise<BigUint64Array>; | 100 | 100 | function fstat(fd: number, useBigint: false, usePromises: typeof kUsePromises): Promise<Float64Array>; | @@ -127,9 +127,9 @@ declare namespace InternalFSBinding { | 127 | 127 | function lstat(path: StringOrBuffer, useBigint: boolean, req: FSReqCallback<Float64Array | BigUint64Array>): void; | 128 | 128 | function lstat(path: StringOrBuffer, useBigint: true, req: FSReqCallback<BigUint64Array>): void; | 129 | 129 | function lstat(path: StringOrBuffer, useBigint: false, req: FSReqCallback<Float64Array>): void; | 130 | | - function lstat(path: StringOrBuffer, useBigint: boolean, req: undefined, ctx: FSSyncContext): Float64Array | BigUint64Array; | 131 | | - function lstat(path: StringOrBuffer, useBigint: true, req: undefined, ctx: FSSyncContext): BigUint64Array; | 132 | | - function lstat(path: StringOrBuffer, useBigint: false, req: undefined, ctx: FSSyncContext): Float64Array; | | 130 | + function lstat(path: StringOrBuffer, useBigint: boolean, req: undefined, throwIfNoEntry: boolean): Float64Array | BigUint64Array; | | 131 | + function lstat(path: StringOrBuffer, useBigint: true, req: undefined, throwIfNoEntry: boolean): BigUint64Array; | | 132 | + function lstat(path: StringOrBuffer, useBigint: false, req: undefined, throwIfNoEntry: boolean): Float64Array; | 133 | 133 | function lstat(path: StringOrBuffer, useBigint: boolean, usePromises: typeof kUsePromises): Promise<Float64Array | BigUint64Array>; | 134 | 134 | function lstat(path: StringOrBuffer, useBigint: true, usePromises: typeof kUsePromises): Promise<BigUint64Array>; | 135 | 135 | function lstat(path: StringOrBuffer, useBigint: false, usePromises: typeof kUsePromises): Promise<Float64Array>; | |
|