Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 92bdfd1

Browse filesBrowse files
benjamingrdanielleadams
authored andcommitted
fs: add support for AbortSignal in readFile
PR-URL: #35911 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: Rich Trott <rtrott@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com>
1 parent d83e253 commit 92bdfd1
Copy full SHA for 92bdfd1

File tree

Expand file treeCollapse file tree

7 files changed

+144
-14
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+144
-14
lines changed
Open diff view settings
Collapse file

‎doc/api/fs.md‎

Copy file name to clipboardExpand all lines: doc/api/fs.md
+42Lines changed: 42 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -3031,6 +3031,10 @@ If `options.withFileTypes` is set to `true`, the result will contain
30313031
<!-- YAML
30323032
added: v0.1.29
30333033
changes:
3034+
- version: REPLACEME
3035+
pr-url: https://github.com/nodejs/node/pull/35911
3036+
description: The options argument may include an AbortSignal to abort an
3037+
ongoing readFile request.
30343038
- version: v10.0.0
30353039
pr-url: https://github.com/nodejs/node/pull/12562
30363040
description: The `callback` parameter is no longer optional. Not passing
@@ -3056,6 +3060,7 @@ changes:
30563060
* `options` {Object|string}
30573061
* `encoding` {string|null} **Default:** `null`
30583062
* `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`.
3063+
* `signal` {AbortSignal} allows aborting an in-progress readFile
30593064
* `callback` {Function}
30603065
* `err` {Error}
30613066
* `data` {string|Buffer}
@@ -3097,9 +3102,25 @@ fs.readFile('<directory>', (err, data) => {
30973102
});
30983103
```
30993104

3105+
It is possible to abort an ongoing request using an `AbortSignal`. If a
3106+
request is aborted the callback is called with an `AbortError`:
3107+
3108+
```js
3109+
const controller = new AbortController();
3110+
const signal = controller.signal;
3111+
fs.readFile(fileInfo[0].name, { signal }, (err, buf) => {
3112+
// ...
3113+
});
3114+
// When you want to abort the request
3115+
controller.abort();
3116+
```
3117+
31003118
The `fs.readFile()` function buffers the entire file. To minimize memory costs,
31013119
when possible prefer streaming via `fs.createReadStream()`.
31023120

3121+
Aborting an ongoing request does not abort individual operating
3122+
system requests but rather the internal buffering `fs.readFile` performs.
3123+
31033124
### File descriptors
31043125

31053126
1. Any specified file descriptor has to support reading.
@@ -4771,6 +4792,7 @@ added: v10.0.0
47714792

47724793
* `options` {Object|string}
47734794
* `encoding` {string|null} **Default:** `null`
4795+
* `signal` {AbortSignal} allows aborting an in-progress readFile
47744796
* Returns: {Promise}
47754797

47764798
Asynchronously reads the entire contents of a file.
@@ -5438,12 +5460,18 @@ print('./').catch(console.error);
54385460
### `fsPromises.readFile(path[, options])`
54395461
<!-- YAML
54405462
added: v10.0.0
5463+
changes:
5464+
- version: REPLACEME
5465+
pr-url: https://github.com/nodejs/node/pull/35911
5466+
description: The options argument may include an AbortSignal to abort an
5467+
ongoing readFile request.
54415468
-->
54425469

54435470
* `path` {string|Buffer|URL|FileHandle} filename or `FileHandle`
54445471
* `options` {Object|string}
54455472
* `encoding` {string|null} **Default:** `null`
54465473
* `flag` {string} See [support of file system `flags`][]. **Default:** `'r'`.
5474+
* `signal` {AbortSignal} allows aborting an in-progress readFile
54475475
* Returns: {Promise}
54485476

54495477
Asynchronously reads the entire contents of a file.
@@ -5459,6 +5487,20 @@ platform-specific. On macOS, Linux, and Windows, the promise will be rejected
54595487
with an error. On FreeBSD, a representation of the directory's contents will be
54605488
returned.
54615489

5490+
It is possible to abort an ongoing `readFile` using an `AbortSignal`. If a
5491+
request is aborted the promise returned is rejected with an `AbortError`:
5492+
5493+
```js
5494+
const controller = new AbortController();
5495+
const signal = controller.signal;
5496+
readFile(fileName, { signal }).then((file) => { /* ... */ });
5497+
// Abort the request
5498+
controller.abort();
5499+
```
5500+
5501+
Aborting an ongoing request does not abort individual operating
5502+
system requests but rather the internal buffering `fs.readFile` performs.
5503+
54625504
Any specified `FileHandle` has to support reading.
54635505

54645506
### `fsPromises.readlink(path[, options])`
Collapse file

‎lib/fs.js‎

Copy file name to clipboardExpand all lines: lib/fs.js
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@ function readFile(path, options, callback) {
315315
const context = new ReadFileContext(callback, options.encoding);
316316
context.isUserFd = isFd(path); // File descriptor ownership
317317

318+
if (options.signal) {
319+
context.signal = options.signal;
320+
}
318321
if (context.isUserFd) {
319322
process.nextTick(function tick(context) {
320323
readFileAfterOpen.call({ context }, null, path);
Collapse file

‎lib/internal/fs/promises.js‎

Copy file name to clipboardExpand all lines: lib/internal/fs/promises.js
+23-2Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ const {
2929
} = internalBinding('constants').fs;
3030
const binding = internalBinding('fs');
3131
const { Buffer } = require('buffer');
32+
33+
const { codes, hideStackFrames } = require('internal/errors');
3234
const {
3335
ERR_FS_FILE_TOO_LARGE,
3436
ERR_INVALID_ARG_TYPE,
3537
ERR_INVALID_ARG_VALUE,
36-
ERR_METHOD_NOT_IMPLEMENTED
37-
} = require('internal/errors').codes;
38+
ERR_METHOD_NOT_IMPLEMENTED,
39+
} = codes;
3840
const { isArrayBufferView } = require('internal/util/types');
3941
const { rimrafPromises } = require('internal/fs/rimraf');
4042
const {
@@ -82,6 +84,13 @@ const {
8284
const getDirectoryEntriesPromise = promisify(getDirents);
8385
const validateRmOptionsPromise = promisify(validateRmOptions);
8486

87+
let DOMException;
88+
const lazyDOMException = hideStackFrames((message, name) => {
89+
if (DOMException === undefined)
90+
DOMException = internalBinding('messaging').DOMException;
91+
return new DOMException(message, name);
92+
});
93+
8594
class FileHandle extends JSTransferable {
8695
constructor(filehandle) {
8796
super();
@@ -259,8 +268,17 @@ async function writeFileHandle(filehandle, data) {
259268
}
260269

261270
async function readFileHandle(filehandle, options) {
271+
const signal = options && options.signal;
272+
273+
if (signal && signal.aborted) {
274+
throw lazyDOMException('The operation was aborted', 'AbortError');
275+
}
262276
const statFields = await binding.fstat(filehandle.fd, false, kUsePromises);
263277

278+
if (signal && signal.aborted) {
279+
throw lazyDOMException('The operation was aborted', 'AbortError');
280+
}
281+
264282
let size;
265283
if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) {
266284
size = statFields[8/* size */];
@@ -277,6 +295,9 @@ async function readFileHandle(filehandle, options) {
277295
MathMin(size, kReadFileMaxChunkSize);
278296
let endOfFile = false;
279297
do {
298+
if (signal && signal.aborted) {
299+
throw lazyDOMException('The operation was aborted', 'AbortError');
300+
}
280301
const buf = Buffer.alloc(chunkSize);
281302
const { bytesRead, buffer } =
282303
await read(filehandle, buf, 0, chunkSize, -1);
Collapse file

‎lib/internal/fs/read_file_context.js‎

Copy file name to clipboardExpand all lines: lib/internal/fs/read_file_context.js
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ const { Buffer } = require('buffer');
88

99
const { FSReqCallback, close, read } = internalBinding('fs');
1010

11+
const { hideStackFrames } = require('internal/errors');
12+
13+
14+
let DOMException;
15+
const lazyDOMException = hideStackFrames((message, name) => {
16+
if (DOMException === undefined)
17+
DOMException = internalBinding('messaging').DOMException;
18+
return new DOMException(message, name);
19+
});
20+
1121
// Use 64kb in case the file type is not a regular file and thus do not know the
1222
// actual file size. Increasing the value further results in more frequent over
1323
// allocation for small files and consumes CPU time and memory that should be
@@ -74,13 +84,19 @@ class ReadFileContext {
7484
this.pos = 0;
7585
this.encoding = encoding;
7686
this.err = null;
87+
this.signal = undefined;
7788
}
7889

7990
read() {
8091
let buffer;
8192
let offset;
8293
let length;
8394

95+
if (this.signal && this.signal.aborted) {
96+
return this.close(
97+
lazyDOMException('The operation was aborted', 'AbortError')
98+
);
99+
}
84100
if (this.size === 0) {
85101
buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength);
86102
offset = 0;
Collapse file

‎lib/internal/fs/utils.js‎

Copy file name to clipboardExpand all lines: lib/internal/fs/utils.js
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const {
3535
const { once } = require('internal/util');
3636
const { toPathIfFileURL } = require('internal/url');
3737
const {
38+
validateAbortSignal,
3839
validateBoolean,
3940
validateInt32,
4041
validateUint32
@@ -296,6 +297,10 @@ function getOptions(options, defaultOptions) {
296297

297298
if (options.encoding !== 'buffer')
298299
assertEncoding(options.encoding);
300+
301+
if (options.signal !== undefined) {
302+
validateAbortSignal(options.signal, 'options.signal');
303+
}
299304
return options;
300305
}
301306

Collapse file

‎test/parallel/test-fs-promises-readfile.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-fs-promises-readfile.js
+37-12Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,21 @@ tmpdir.refresh();
1010

1111
const fn = path.join(tmpdir.path, 'large-file');
1212

13-
async function validateReadFile() {
14-
// Creating large buffer with random content
15-
const buffer = Buffer.from(
16-
Array.apply(null, { length: 16834 * 2 })
17-
.map(Math.random)
18-
.map((number) => (number * (1 << 8)))
19-
);
13+
// Creating large buffer with random content
14+
const largeBuffer = Buffer.from(
15+
Array.apply(null, { length: 16834 * 2 })
16+
.map(Math.random)
17+
.map((number) => (number * (1 << 8)))
18+
);
2019

20+
async function createLargeFile() {
2121
// Writing buffer to a file then try to read it
22-
await writeFile(fn, buffer);
22+
await writeFile(fn, largeBuffer);
23+
}
24+
25+
async function validateReadFile() {
2326
const readBuffer = await readFile(fn);
24-
assert.strictEqual(readBuffer.equals(buffer), true);
27+
assert.strictEqual(readBuffer.equals(largeBuffer), true);
2528
}
2629

2730
async function validateReadFileProc() {
@@ -39,6 +42,28 @@ async function validateReadFileProc() {
3942
assert.ok(hostname.length > 0);
4043
}
4144

42-
validateReadFile()
43-
.then(() => validateReadFileProc())
44-
.then(common.mustCall());
45+
function validateReadFileAbortLogicBefore() {
46+
const controller = new AbortController();
47+
const signal = controller.signal;
48+
controller.abort();
49+
assert.rejects(readFile(fn, { signal }), {
50+
name: 'AbortError'
51+
});
52+
}
53+
54+
function validateReadFileAbortLogicDuring() {
55+
const controller = new AbortController();
56+
const signal = controller.signal;
57+
process.nextTick(() => controller.abort());
58+
assert.rejects(readFile(fn, { signal }), {
59+
name: 'AbortError'
60+
});
61+
}
62+
63+
(async () => {
64+
await createLargeFile();
65+
await validateReadFile();
66+
await validateReadFileProc();
67+
await validateReadFileAbortLogicBefore();
68+
await validateReadFileAbortLogicDuring();
69+
})().then(common.mustCall());
Collapse file

‎test/parallel/test-fs-readfile.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-fs-readfile.js
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,21 @@ for (const e of fileInfo) {
5757
assert.deepStrictEqual(buf, e.contents);
5858
}));
5959
}
60+
{
61+
// Test cancellation, before
62+
const controller = new AbortController();
63+
const signal = controller.signal;
64+
controller.abort();
65+
fs.readFile(fileInfo[0].name, { signal }, common.mustCall((err, buf) => {
66+
assert.strictEqual(err.name, 'AbortError');
67+
}));
68+
}
69+
{
70+
// Test cancellation, during read
71+
const controller = new AbortController();
72+
const signal = controller.signal;
73+
fs.readFile(fileInfo[0].name, { signal }, common.mustCall((err, buf) => {
74+
assert.strictEqual(err.name, 'AbortError');
75+
}));
76+
process.nextTick(() => controller.abort());
77+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.