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 e01c1d7

Browse filesBrowse files
authored
fs: add flush option to writeFile() functions
This commit adds a 'flush' option to the fs.writeFile family of functions. Refs: #49886 PR-URL: #50009 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name> Reviewed-By: Daijiro Wachi <daijiro.wachi@gmail.com>
1 parent 557044a commit e01c1d7
Copy full SHA for e01c1d7

File tree

Expand file treeCollapse file tree

4 files changed

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

4 files changed

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

‎doc/api/fs.md‎

Copy file name to clipboardExpand all lines: doc/api/fs.md
+18-2Lines changed: 18 additions & 2 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1742,6 +1742,9 @@ All the [caveats][] for `fs.watch()` also apply to `fsPromises.watch()`.
17421742
<!-- YAML
17431743
added: v10.0.0
17441744
changes:
1745+
- version: REPLACEME
1746+
pr-url: https://github.com/nodejs/node/pull/50009
1747+
description: The `flush` option is now supported.
17451748
- version:
17461749
- v15.14.0
17471750
- v14.18.0
@@ -1765,6 +1768,9 @@ changes:
17651768
* `encoding` {string|null} **Default:** `'utf8'`
17661769
* `mode` {integer} **Default:** `0o666`
17671770
* `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`.
1771+
* `flush` {boolean} If all data is successfully written to the file, and
1772+
`flush` is `true`, `filehandle.sync()` is used to flush the data.
1773+
**Default:** `false`.
17681774
* `signal` {AbortSignal} allows aborting an in-progress writeFile
17691775
* Returns: {Promise} Fulfills with `undefined` upon success.
17701776
@@ -4879,6 +4885,9 @@ details.
48794885
<!-- YAML
48804886
added: v0.1.29
48814887
changes:
4888+
- version: REPLACEME
4889+
pr-url: https://github.com/nodejs/node/pull/50009
4890+
description: The `flush` option is now supported.
48824891
- version: v19.0.0
48834892
pr-url: https://github.com/nodejs/node/pull/42796
48844893
description: Passing to the `string` parameter an object with an own
@@ -4936,6 +4945,9 @@ changes:
49364945
* `encoding` {string|null} **Default:** `'utf8'`
49374946
* `mode` {integer} **Default:** `0o666`
49384947
* `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`.
4948+
* `flush` {boolean} If all data is successfully written to the file, and
4949+
`flush` is `true`, `fs.fsync()` is used to flush the data.
4950+
**Default:** `false`.
49394951
* `signal` {AbortSignal} allows aborting an in-progress writeFile
49404952
* `callback` {Function}
49414953
* `err` {Error|AggregateError}
@@ -6167,6 +6179,9 @@ this API: [`fs.utimes()`][].
61676179
<!-- YAML
61686180
added: v0.1.29
61696181
changes:
6182+
- version: REPLACEME
6183+
pr-url: https://github.com/nodejs/node/pull/50009
6184+
description: The `flush` option is now supported.
61706185
- version: v19.0.0
61716186
pr-url: https://github.com/nodejs/node/pull/42796
61726187
description: Passing to the `data` parameter an object with an own
@@ -6201,8 +6216,9 @@ changes:
62016216
* `encoding` {string|null} **Default:** `'utf8'`
62026217
* `mode` {integer} **Default:** `0o666`
62036218
* `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`.
6204-
6205-
Returns `undefined`.
6219+
* `flush` {boolean} If all data is successfully written to the file, and
6220+
`flush` is `true`, `fs.fsyncSync()` is used to flush the data.
6221+
Returns `undefined`.
62066222

62076223
The `mode` option only affects the newly created file. See [`fs.open()`][]
62086224
for more details.
Collapse file

‎lib/fs.js‎

Copy file name to clipboardExpand all lines: lib/fs.js
+50-9Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2216,7 +2216,7 @@ function lutimesSync(path, atime, mtime) {
22162216
handleErrorFromBinding(ctx);
22172217
}
22182218

2219-
function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) {
2219+
function writeAll(fd, isUserFd, buffer, offset, length, signal, flush, callback) {
22202220
if (signal?.aborted) {
22212221
const abortError = new AbortError(undefined, { cause: signal?.reason });
22222222
if (isUserFd) {
@@ -2239,15 +2239,33 @@ function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) {
22392239
});
22402240
}
22412241
} else if (written === length) {
2242-
if (isUserFd) {
2243-
callback(null);
2242+
if (!flush) {
2243+
if (isUserFd) {
2244+
callback(null);
2245+
} else {
2246+
fs.close(fd, callback);
2247+
}
22442248
} else {
2245-
fs.close(fd, callback);
2249+
fs.fsync(fd, (syncErr) => {
2250+
if (syncErr) {
2251+
if (isUserFd) {
2252+
callback(syncErr);
2253+
} else {
2254+
fs.close(fd, (err) => {
2255+
callback(aggregateTwoErrors(err, syncErr));
2256+
});
2257+
}
2258+
} else if (isUserFd) {
2259+
callback(null);
2260+
} else {
2261+
fs.close(fd, callback);
2262+
}
2263+
});
22462264
}
22472265
} else {
22482266
offset += written;
22492267
length -= written;
2250-
writeAll(fd, isUserFd, buffer, offset, length, signal, callback);
2268+
writeAll(fd, isUserFd, buffer, offset, length, signal, flush, callback);
22512269
}
22522270
});
22532271
}
@@ -2261,14 +2279,23 @@ function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) {
22612279
* mode?: number;
22622280
* flag?: string;
22632281
* signal?: AbortSignal;
2282+
* flush?: boolean;
22642283
* } | string} [options]
22652284
* @param {(err?: Error) => any} callback
22662285
* @returns {void}
22672286
*/
22682287
function writeFile(path, data, options, callback) {
22692288
callback = maybeCallback(callback || options);
2270-
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
2289+
options = getOptions(options, {
2290+
encoding: 'utf8',
2291+
mode: 0o666,
2292+
flag: 'w',
2293+
flush: false,
2294+
});
22712295
const flag = options.flag || 'w';
2296+
const flush = options.flush ?? false;
2297+
2298+
validateBoolean(flush, 'options.flush');
22722299

22732300
if (!isArrayBufferView(data)) {
22742301
validateStringAfterArrayBufferView(data, 'data');
@@ -2278,7 +2305,7 @@ function writeFile(path, data, options, callback) {
22782305
if (isFd(path)) {
22792306
const isUserFd = true;
22802307
const signal = options.signal;
2281-
writeAll(path, isUserFd, data, 0, data.byteLength, signal, callback);
2308+
writeAll(path, isUserFd, data, 0, data.byteLength, signal, flush, callback);
22822309
return;
22832310
}
22842311

@@ -2291,7 +2318,7 @@ function writeFile(path, data, options, callback) {
22912318
} else {
22922319
const isUserFd = false;
22932320
const signal = options.signal;
2294-
writeAll(fd, isUserFd, data, 0, data.byteLength, signal, callback);
2321+
writeAll(fd, isUserFd, data, 0, data.byteLength, signal, flush, callback);
22952322
}
22962323
});
22972324
}
@@ -2304,11 +2331,21 @@ function writeFile(path, data, options, callback) {
23042331
* encoding?: string | null;
23052332
* mode?: number;
23062333
* flag?: string;
2334+
* flush?: boolean;
23072335
* } | string} [options]
23082336
* @returns {void}
23092337
*/
23102338
function writeFileSync(path, data, options) {
2311-
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
2339+
options = getOptions(options, {
2340+
encoding: 'utf8',
2341+
mode: 0o666,
2342+
flag: 'w',
2343+
flush: false,
2344+
});
2345+
2346+
const flush = options.flush ?? false;
2347+
2348+
validateBoolean(flush, 'options.flush');
23122349

23132350
if (!isArrayBufferView(data)) {
23142351
validateStringAfterArrayBufferView(data, 'data');
@@ -2328,6 +2365,10 @@ function writeFileSync(path, data, options) {
23282365
offset += written;
23292366
length -= written;
23302367
}
2368+
2369+
if (flush) {
2370+
fs.fsyncSync(fd);
2371+
}
23312372
} finally {
23322373
if (!isUserFd) fs.closeSync(fd);
23332374
}
Collapse file

‎lib/internal/fs/promises.js‎

Copy file name to clipboardExpand all lines: lib/internal/fs/promises.js
+28-3Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,18 @@ async function handleFdClose(fileOpPromise, closeFunc) {
414414
);
415415
}
416416

417+
async function handleFdSync(fileOpPromise, handle) {
418+
return PromisePrototypeThen(
419+
fileOpPromise,
420+
(result) => PromisePrototypeThen(
421+
handle.sync(),
422+
() => result,
423+
(syncError) => PromiseReject(syncError),
424+
),
425+
(opError) => PromiseReject(opError),
426+
);
427+
}
428+
417429
async function fsCall(fn, handle, ...args) {
418430
assert(handle[kRefs] !== undefined,
419431
'handle must be an instance of FileHandle');
@@ -1007,8 +1019,16 @@ async function mkdtemp(prefix, options) {
10071019
}
10081020

10091021
async function writeFile(path, data, options) {
1010-
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
1022+
options = getOptions(options, {
1023+
encoding: 'utf8',
1024+
mode: 0o666,
1025+
flag: 'w',
1026+
flush: false,
1027+
});
10111028
const flag = options.flag || 'w';
1029+
const flush = options.flush ?? false;
1030+
1031+
validateBoolean(flush, 'options.flush');
10121032

10131033
if (!isArrayBufferView(data) && !isCustomIterable(data)) {
10141034
validateStringAfterArrayBufferView(data, 'data');
@@ -1022,8 +1042,13 @@ async function writeFile(path, data, options) {
10221042
checkAborted(options.signal);
10231043

10241044
const fd = await open(path, flag, options.mode);
1025-
return handleFdClose(
1026-
writeFileHandle(fd, data, options.signal, options.encoding), fd.close);
1045+
let writeOp = writeFileHandle(fd, data, options.signal, options.encoding);
1046+
1047+
if (flush) {
1048+
writeOp = handleFdSync(writeOp, fd);
1049+
}
1050+
1051+
return handleFdClose(writeOp, fd.close);
10271052
}
10281053

10291054
function isCustomIterable(obj) {
Collapse file
+114Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
'use strict';
2+
const common = require('../common');
3+
const tmpdir = require('../common/tmpdir');
4+
const assert = require('node:assert');
5+
const fs = require('node:fs');
6+
const fsp = require('node:fs/promises');
7+
const test = require('node:test');
8+
const data = 'foo';
9+
let cnt = 0;
10+
11+
function nextFile() {
12+
return tmpdir.resolve(`${cnt++}.out`);
13+
}
14+
15+
tmpdir.refresh();
16+
17+
test('synchronous version', async (t) => {
18+
await t.test('validation', (t) => {
19+
for (const v of ['true', '', 0, 1, [], {}, Symbol()]) {
20+
assert.throws(() => {
21+
fs.writeFileSync(nextFile(), data, { flush: v });
22+
}, { code: 'ERR_INVALID_ARG_TYPE' });
23+
}
24+
});
25+
26+
await t.test('performs flush', (t) => {
27+
const spy = t.mock.method(fs, 'fsyncSync');
28+
const file = nextFile();
29+
fs.writeFileSync(file, data, { flush: true });
30+
const calls = spy.mock.calls;
31+
assert.strictEqual(calls.length, 1);
32+
assert.strictEqual(calls[0].result, undefined);
33+
assert.strictEqual(calls[0].error, undefined);
34+
assert.strictEqual(calls[0].arguments.length, 1);
35+
assert.strictEqual(typeof calls[0].arguments[0], 'number');
36+
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
37+
});
38+
39+
await t.test('does not perform flush', (t) => {
40+
const spy = t.mock.method(fs, 'fsyncSync');
41+
42+
for (const v of [undefined, null, false]) {
43+
const file = nextFile();
44+
fs.writeFileSync(file, data, { flush: v });
45+
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
46+
}
47+
48+
assert.strictEqual(spy.mock.calls.length, 0);
49+
});
50+
});
51+
52+
test('callback version', async (t) => {
53+
await t.test('validation', (t) => {
54+
for (const v of ['true', '', 0, 1, [], {}, Symbol()]) {
55+
assert.throws(() => {
56+
fs.writeFileSync(nextFile(), data, { flush: v });
57+
}, { code: 'ERR_INVALID_ARG_TYPE' });
58+
}
59+
});
60+
61+
await t.test('performs flush', (t, done) => {
62+
const spy = t.mock.method(fs, 'fsync');
63+
const file = nextFile();
64+
fs.writeFile(file, data, { flush: true }, common.mustSucceed(() => {
65+
const calls = spy.mock.calls;
66+
assert.strictEqual(calls.length, 1);
67+
assert.strictEqual(calls[0].result, undefined);
68+
assert.strictEqual(calls[0].error, undefined);
69+
assert.strictEqual(calls[0].arguments.length, 2);
70+
assert.strictEqual(typeof calls[0].arguments[0], 'number');
71+
assert.strictEqual(typeof calls[0].arguments[1], 'function');
72+
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
73+
done();
74+
}));
75+
});
76+
77+
await t.test('does not perform flush', (t, done) => {
78+
const values = [undefined, null, false];
79+
const spy = t.mock.method(fs, 'fsync');
80+
let cnt = 0;
81+
82+
for (const v of values) {
83+
const file = nextFile();
84+
85+
fs.writeFile(file, data, { flush: v }, common.mustSucceed(() => {
86+
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
87+
cnt++;
88+
89+
if (cnt === values.length) {
90+
assert.strictEqual(spy.mock.calls.length, 0);
91+
done();
92+
}
93+
}));
94+
}
95+
});
96+
});
97+
98+
test('promise based version', async (t) => {
99+
await t.test('validation', async (t) => {
100+
for (const v of ['true', '', 0, 1, [], {}, Symbol()]) {
101+
await assert.rejects(() => {
102+
return fsp.writeFile(nextFile(), data, { flush: v });
103+
}, { code: 'ERR_INVALID_ARG_TYPE' });
104+
}
105+
});
106+
107+
await t.test('success path', async (t) => {
108+
for (const v of [undefined, null, false, true]) {
109+
const file = nextFile();
110+
await fsp.writeFile(file, data, { flush: v });
111+
assert.strictEqual(await fsp.readFile(file, 'utf8'), data);
112+
}
113+
});
114+
});

0 commit comments

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