From 7c665282b46e6096327fb14184d2aedf1db1a1a0 Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Mon, 2 Jun 2025 01:20:35 +0300 Subject: [PATCH] fs: add signal option to fs.stat() --- doc/api/fs.md | 10 ++++--- lib/fs.js | 10 ++++++- test/parallel/test-fs-stat-abort-test.js | 34 ++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 test/parallel/test-fs-stat-abort-test.js diff --git a/doc/api/fs.md b/doc/api/fs.md index ff52cfa5a36e21..4418ee81442efe 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -624,15 +624,17 @@ Read from a file and write to an array of {ArrayBufferView}s * `options` {Object} - * `bigint` {boolean} Whether the numeric values in the returned - {fs.Stats} object should be `bigint`. **Default:** `false`. + * `bigint` {boolean} Whether the numeric values in the returned {fs.Stats} object should be `bigint`. **Default:** `false`. + * `signal` {AbortSignal} An AbortSignal to cancel the operation. **Default:** `undefined`. * Returns: {Promise} Fulfills with an {fs.Stats} for the file. #### `filehandle.sync()` diff --git a/lib/fs.js b/lib/fs.js index ebe29c578d37d0..2dd10cbce7e13b 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1619,7 +1619,7 @@ function lstat(path, options = { bigint: false }, callback) { /** * Asynchronously gets the stats of a file. * @param {string | Buffer | URL} path - * @param {{ bigint?: boolean; }} [options] + * @param {{ bigint?: boolean, signal?: AbortSignal }} [options] * @param {( * err?: Error, * stats?: Stats @@ -1630,8 +1630,16 @@ function stat(path, options = { bigint: false }, callback) { if (typeof options === 'function') { callback = options; options = kEmptyObject; + } else if (options === null || typeof options !== 'object') { + options = kEmptyObject; + } else { + options = getOptions(options, { bigint: false }); } + callback = makeStatsCallback(callback); + path = getValidatedPath(path); + + if (checkAborted(options.signal, callback)) return; const req = new FSReqCallback(options.bigint); req.oncomplete = callback; diff --git a/test/parallel/test-fs-stat-abort-test.js b/test/parallel/test-fs-stat-abort-test.js new file mode 100644 index 00000000000000..2a2b35f8030d7f --- /dev/null +++ b/test/parallel/test-fs-stat-abort-test.js @@ -0,0 +1,34 @@ +'use strict'; + +require('../common'); +const test = require('node:test'); +const assert = require('node:assert'); +const fs = require('node:fs'); +const tmpdir = require('../common/tmpdir'); + +test('fs.stat should throw AbortError when called with an already aborted AbortSignal', async () => { + // This test verifies that fs.stat immediately throws an AbortError if the provided AbortSignal + // has already been canceled. This approach is used because trying to abort an fs.stat call in flight + // is unreliable given that file system operations tend to complete very quickly on many platforms. + tmpdir.refresh(); + + const filePath = tmpdir.resolve('temp.txt'); + fs.writeFileSync(filePath, 'Test'); + + // Create an already aborted AbortSignal. + const signal = AbortSignal.abort(); + + const { promise, resolve, reject } = Promise.withResolvers(); + fs.stat(filePath, { signal }, (err, stats) => { + if (err) { + return reject(err); + } + resolve(stats); + }); + + // Assert that the promise is rejected with an AbortError. + await assert.rejects(promise, { name: 'AbortError' }); + + fs.unlinkSync(filePath); + tmpdir.refresh(); +});