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 ccf9f0e

Browse filesBrowse files
benjamingrdanielleadams
authored andcommitted
fs: support abortsignal in writeFile
PR-URL: #35993 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent dcb2760 commit ccf9f0e
Copy full SHA for ccf9f0e

File tree

Expand file treeCollapse file tree

5 files changed

+130
-7
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+130
-7
lines changed
Open diff view settings
Collapse file

‎doc/api/fs.md‎

Copy file name to clipboardExpand all lines: doc/api/fs.md
+60-1Lines changed: 60 additions & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -4385,6 +4385,10 @@ details.
43854385
<!-- YAML
43864386
added: v0.1.29
43874387
changes:
4388+
- version: REPLACEME
4389+
pr-url: https://github.com/nodejs/node/pull/35993
4390+
description: The options argument may include an AbortSignal to abort an
4391+
ongoing writeFile request.
43884392
- version: v14.12.0
43894393
pr-url: https://github.com/nodejs/node/pull/34993
43904394
description: The `data` parameter will stringify an object with an
@@ -4419,6 +4423,7 @@ changes:
44194423
* `encoding` {string|null} **Default:** `'utf8'`
44204424
* `mode` {integer} **Default:** `0o666`
44214425
* `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`.
4426+
* `signal` {AbortSignal} allows aborting an in-progress writeFile
44224427
* `callback` {Function}
44234428
* `err` {Error}
44244429

@@ -4450,6 +4455,28 @@ It is unsafe to use `fs.writeFile()` multiple times on the same file without
44504455
waiting for the callback. For this scenario, [`fs.createWriteStream()`][] is
44514456
recommended.
44524457

4458+
Similarly to `fs.readFile` - `fs.writeFile` is a convenience method that
4459+
performs multiple `write` calls internally to write the buffer passed to it.
4460+
For performance sensitive code consider using [`fs.createWriteStream()`][].
4461+
4462+
It is possible to use an {AbortSignal} to cancel an `fs.writeFile()`.
4463+
Cancelation is "best effort", and some amount of data is likely still
4464+
to be written.
4465+
4466+
```js
4467+
const controller = new AbortController();
4468+
const { signal } = controller;
4469+
const data = new Uint8Array(Buffer.from('Hello Node.js'));
4470+
fs.writeFile('message.txt', data, { signal }, (err) => {
4471+
// When a request is aborted - the callback is called with an AbortError
4472+
});
4473+
// When the request should be aborted
4474+
controller.abort();
4475+
```
4476+
4477+
Aborting an ongoing request does not abort individual operating
4478+
system requests but rather the internal buffering `fs.writeFile` performs.
4479+
44534480
### Using `fs.writeFile()` with file descriptors
44544481

44554482
When `file` is a file descriptor, the behavior is almost identical to directly
@@ -5717,6 +5744,10 @@ The `atime` and `mtime` arguments follow these rules:
57175744
<!-- YAML
57185745
added: v10.0.0
57195746
changes:
5747+
- version: REPLACEME
5748+
pr-url: https://github.com/nodejs/node/pull/35993
5749+
description: The options argument may include an AbortSignal to abort an
5750+
ongoing writeFile request.
57205751
- version: v14.12.0
57215752
pr-url: https://github.com/nodejs/node/pull/34993
57225753
description: The `data` parameter will stringify an object with an
@@ -5733,6 +5764,7 @@ changes:
57335764
* `encoding` {string|null} **Default:** `'utf8'`
57345765
* `mode` {integer} **Default:** `0o666`
57355766
* `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`.
5767+
* `signal` {AbortSignal} allows aborting an in-progress writeFile
57365768
* Returns: {Promise}
57375769

57385770
Asynchronously writes data to a file, replacing the file if it already exists.
@@ -5746,7 +5778,34 @@ If `options` is a string, then it specifies the encoding.
57465778
Any specified `FileHandle` has to support writing.
57475779

57485780
It is unsafe to use `fsPromises.writeFile()` multiple times on the same file
5749-
without waiting for the `Promise` to be resolved (or rejected).
5781+
without waiting for the `Promise` to be fulfilled (or rejected).
5782+
5783+
Similarly to `fsPromises.readFile` - `fsPromises.writeFile` is a convenience
5784+
method that performs multiple `write` calls internally to write the buffer
5785+
passed to it. For performance sensitive code consider using
5786+
[`fs.createWriteStream()`][].
5787+
5788+
It is possible to use an {AbortSignal} to cancel an `fsPromises.writeFile()`.
5789+
Cancelation is "best effort", and some amount of data is likely still
5790+
to be written.
5791+
5792+
```js
5793+
const controller = new AbortController();
5794+
const { signal } = controller;
5795+
const data = new Uint8Array(Buffer.from('Hello Node.js'));
5796+
(async () => {
5797+
try {
5798+
await fs.writeFile('message.txt', data, { signal });
5799+
} catch (err) {
5800+
// When a request is aborted - err is an AbortError
5801+
}
5802+
})();
5803+
// When the request should be aborted
5804+
controller.abort();
5805+
```
5806+
5807+
Aborting an ongoing request does not abort individual operating
5808+
system requests but rather the internal buffering `fs.writeFile` performs.
57505809

57515810
## FS constants
57525811

Collapse file

‎lib/fs.js‎

Copy file name to clipboardExpand all lines: lib/fs.js
+22-4Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const {
7171
ERR_INVALID_CALLBACK,
7272
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM
7373
},
74+
hideStackFrames,
7475
uvException
7576
} = require('internal/errors');
7677

@@ -133,6 +134,13 @@ let ReadStream;
133134
let WriteStream;
134135
let rimraf;
135136
let rimrafSync;
137+
let DOMException;
138+
139+
const lazyDOMException = hideStackFrames((message, name) => {
140+
if (DOMException === undefined)
141+
DOMException = internalBinding('messaging').DOMException;
142+
return new DOMException(message, name);
143+
});
136144

137145
// These have to be separate because of how graceful-fs happens to do it's
138146
// monkeypatching.
@@ -1409,7 +1417,11 @@ function lutimesSync(path, atime, mtime) {
14091417
handleErrorFromBinding(ctx);
14101418
}
14111419

1412-
function writeAll(fd, isUserFd, buffer, offset, length, callback) {
1420+
function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) {
1421+
if (signal?.aborted) {
1422+
callback(lazyDOMException('The operation was aborted', 'AbortError'));
1423+
return;
1424+
}
14131425
// write(fd, buffer, offset, length, position, callback)
14141426
fs.write(fd, buffer, offset, length, null, (writeErr, written) => {
14151427
if (writeErr) {
@@ -1429,7 +1441,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, callback) {
14291441
} else {
14301442
offset += written;
14311443
length -= written;
1432-
writeAll(fd, isUserFd, buffer, offset, length, callback);
1444+
writeAll(fd, isUserFd, buffer, offset, length, signal, callback);
14331445
}
14341446
});
14351447
}
@@ -1446,16 +1458,22 @@ function writeFile(path, data, options, callback) {
14461458

14471459
if (isFd(path)) {
14481460
const isUserFd = true;
1449-
writeAll(path, isUserFd, data, 0, data.byteLength, callback);
1461+
const signal = options.signal;
1462+
writeAll(path, isUserFd, data, 0, data.byteLength, signal, callback);
14501463
return;
14511464
}
14521465

1466+
if (options.signal?.aborted) {
1467+
callback(lazyDOMException('The operation was aborted', 'AbortError'));
1468+
return;
1469+
}
14531470
fs.open(path, flag, options.mode, (openErr, fd) => {
14541471
if (openErr) {
14551472
callback(openErr);
14561473
} else {
14571474
const isUserFd = false;
1458-
writeAll(fd, isUserFd, data, 0, data.byteLength, callback);
1475+
const signal = options.signal;
1476+
writeAll(fd, isUserFd, data, 0, data.byteLength, signal, callback);
14591477
}
14601478
});
14611479
}
Collapse file

‎lib/internal/fs/promises.js‎

Copy file name to clipboardExpand all lines: lib/internal/fs/promises.js
+8-2Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,15 @@ async function fsCall(fn, handle, ...args) {
250250
}
251251
}
252252

253-
async function writeFileHandle(filehandle, data) {
253+
async function writeFileHandle(filehandle, data, signal) {
254254
// `data` could be any kind of typed array.
255255
data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
256256
let remaining = data.length;
257257
if (remaining === 0) return;
258258
do {
259+
if (signal?.aborted) {
260+
throw new lazyDOMException('The operation was aborted', 'AbortError');
261+
}
259262
const { bytesWritten } =
260263
await write(filehandle, data, 0,
261264
MathMin(kWriteFileMaxChunkSize, data.length));
@@ -633,9 +636,12 @@ async function writeFile(path, data, options) {
633636
}
634637

635638
if (path instanceof FileHandle)
636-
return writeFileHandle(path, data);
639+
return writeFileHandle(path, data, options.signal);
637640

638641
const fd = await open(path, flag, options.mode);
642+
if (options.signal?.aborted) {
643+
throw new lazyDOMException('The operation was aborted', 'AbortError');
644+
}
639645
return PromisePrototypeFinally(writeFileHandle(fd, data), fd.close);
640646
}
641647

Collapse file

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

Copy file name to clipboardExpand all lines: test/parallel/test-fs-promises-writefile.js
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const tmpDir = tmpdir.path;
1111
tmpdir.refresh();
1212

1313
const dest = path.resolve(tmpDir, 'tmp.txt');
14+
const otherDest = path.resolve(tmpDir, 'tmp-2.txt');
1415
const buffer = Buffer.from('abc'.repeat(1000));
1516
const buffer2 = Buffer.from('xyz'.repeat(1000));
1617

@@ -20,6 +21,15 @@ async function doWrite() {
2021
assert.deepStrictEqual(data, buffer);
2122
}
2223

24+
async function doWriteWithCancel() {
25+
const controller = new AbortController();
26+
const { signal } = controller;
27+
process.nextTick(() => controller.abort());
28+
assert.rejects(fsPromises.writeFile(otherDest, buffer, { signal }), {
29+
name: 'AbortError'
30+
});
31+
}
32+
2333
async function doAppend() {
2434
await fsPromises.appendFile(dest, buffer2);
2535
const data = fs.readFileSync(dest);
@@ -41,6 +51,7 @@ async function doReadWithEncoding() {
4151
}
4252

4353
doWrite()
54+
.then(doWriteWithCancel)
4455
.then(doAppend)
4556
.then(doRead)
4657
.then(doReadWithEncoding)
Collapse file

‎test/parallel/test-fs-write-file.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-fs-write-file.js
+29Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,32 @@ fs.open(filename4, 'w+', common.mustSucceed((fd) => {
6666
}));
6767
}));
6868
}));
69+
70+
71+
{
72+
// Test that writeFile is cancellable with an AbortSignal.
73+
// Before the operation has started
74+
const controller = new AbortController();
75+
const signal = controller.signal;
76+
const filename3 = join(tmpdir.path, 'test3.txt');
77+
78+
fs.writeFile(filename3, s, { signal }, common.mustCall((err) => {
79+
assert.strictEqual(err.name, 'AbortError');
80+
}));
81+
82+
controller.abort();
83+
}
84+
85+
{
86+
// Test that writeFile is cancellable with an AbortSignal.
87+
// After the operation has started
88+
const controller = new AbortController();
89+
const signal = controller.signal;
90+
const filename4 = join(tmpdir.path, 'test4.txt');
91+
92+
fs.writeFile(filename4, s, { signal }, common.mustCall((err) => {
93+
assert.strictEqual(err.name, 'AbortError');
94+
}));
95+
96+
process.nextTick(() => controller.abort());
97+
}

0 commit comments

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