Skip to content

Commit d1aa62f

Browse files
cjihrigtargos
authored andcommitted
fs: add flush option to createWriteStream()
This commit adds a 'flush' option to the createWriteStream() family of functions. Refs: #49886 PR-URL: #50093 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Marco Ippolito <[email protected]>
1 parent 614c362 commit d1aa62f

File tree

5 files changed

+118
-4
lines changed

5 files changed

+118
-4
lines changed

doc/api/fs.md

+11
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,10 @@ fd.createReadStream({ start: 90, end: 99 });
318318
319319
<!-- YAML
320320
added: v16.11.0
321+
changes:
322+
- version: REPLACEME
323+
pr-url: https://github.com/nodejs/node/pull/50093
324+
description: The `flush` option is now supported.
321325
-->
322326
323327
* `options` {Object}
@@ -326,6 +330,8 @@ added: v16.11.0
326330
* `emitClose` {boolean} **Default:** `true`
327331
* `start` {integer}
328332
* `highWaterMark` {number} **Default:** `16384`
333+
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
334+
prior to closing it. **Default:** `false`.
329335
* Returns: {fs.WriteStream}
330336
331337
`options` may also include a `start` option to allow writing data at some
@@ -2514,6 +2520,9 @@ If `options` is a string, then it specifies the encoding.
25142520
<!-- YAML
25152521
added: v0.1.31
25162522
changes:
2523+
- version: REPLACEME
2524+
pr-url: https://github.com/nodejs/node/pull/50093
2525+
description: The `flush` option is now supported.
25172526
- version: v16.10.0
25182527
pr-url: https://github.com/nodejs/node/pull/40013
25192528
description: The `fs` option does not need `open` method if an `fd` was provided.
@@ -2567,6 +2576,8 @@ changes:
25672576
* `fs` {Object|null} **Default:** `null`
25682577
* `signal` {AbortSignal|null} **Default:** `null`
25692578
* `highWaterMark` {number} **Default:** `16384`
2579+
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
2580+
prior to closing it. **Default:** `false`.
25702581
* Returns: {fs.WriteStream}
25712582
25722583
`options` may also include a `start` option to allow writing data at some

lib/fs.js

+1
Original file line numberDiff line numberDiff line change
@@ -3106,6 +3106,7 @@ function createReadStream(path, options) {
31063106
* emitClose?: boolean;
31073107
* start: number;
31083108
* fs?: object | null;
3109+
* flush?: boolean;
31093110
* }} [options]
31103111
* @returns {WriteStream}
31113112
*/

lib/internal/fs/promises.js

+1
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ class FileHandle extends EventEmitterMixin(JSTransferable) {
349349
* autoClose?: boolean;
350350
* emitClose?: boolean;
351351
* start: number;
352+
* flush?: boolean;
352353
* }} [options]
353354
* @returns {WriteStream}
354355
*/

lib/internal/fs/streams.js

+24-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const {
2323
kEmptyObject,
2424
} = require('internal/util');
2525
const {
26+
validateBoolean,
2627
validateFunction,
2728
validateInteger,
2829
} = require('internal/validators');
@@ -92,6 +93,9 @@ const FileHandleOperations = (handle) => {
9293
PromisePrototypeThen(handle.close(),
9394
() => cb(), cb);
9495
},
96+
fsync: (fd, cb) => {
97+
PromisePrototypeThen(handle.sync(), () => cb(), cb);
98+
},
9599
read: (fd, buf, offset, length, pos, cb) => {
96100
PromisePrototypeThen(handle.read(buf, offset, length, pos),
97101
(r) => cb(null, r.bytesRead, r.buffer),
@@ -113,14 +117,22 @@ const FileHandleOperations = (handle) => {
113117
function close(stream, err, cb) {
114118
if (!stream.fd) {
115119
cb(err);
116-
} else {
117-
stream[kFs].close(stream.fd, (er) => {
118-
cb(er || err);
120+
} else if (stream.flush) {
121+
stream[kFs].fsync(stream.fd, (flushErr) => {
122+
_close(stream, err || flushErr, cb);
119123
});
120-
stream.fd = null;
124+
} else {
125+
_close(stream, err, cb);
121126
}
122127
}
123128

129+
function _close(stream, err, cb) {
130+
stream[kFs].close(stream.fd, (er) => {
131+
cb(er || err);
132+
});
133+
stream.fd = null;
134+
}
135+
124136
function importFd(stream, options) {
125137
if (typeof options.fd === 'number') {
126138
// When fd is a raw descriptor, we must keep our fingers crossed
@@ -350,6 +362,14 @@ function WriteStream(path, options) {
350362
validateFunction(this[kFs].close, 'options.fs.close');
351363
}
352364

365+
this.flush = options.flush;
366+
if (this.flush == null) {
367+
this.flush = false;
368+
} else {
369+
validateBoolean(this.flush, 'options.flush');
370+
validateFunction(this[kFs].fsync, 'options.fs.fsync');
371+
}
372+
353373
// It's enough to override either, in which case only one will be used.
354374
if (!this[kFs].write) {
355375
this._write = null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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('validation', () => {
18+
for (const flush of ['true', '', 0, 1, [], {}, Symbol()]) {
19+
assert.throws(() => {
20+
fs.createWriteStream(nextFile(), { flush });
21+
}, { code: 'ERR_INVALID_ARG_TYPE' });
22+
}
23+
});
24+
25+
test('performs flush', (t, done) => {
26+
const spy = t.mock.method(fs, 'fsync');
27+
const file = nextFile();
28+
const stream = fs.createWriteStream(file, { flush: true });
29+
30+
stream.write(data, common.mustSucceed(() => {
31+
stream.close(common.mustSucceed(() => {
32+
const calls = spy.mock.calls;
33+
assert.strictEqual(calls.length, 1);
34+
assert.strictEqual(calls[0].result, undefined);
35+
assert.strictEqual(calls[0].error, undefined);
36+
assert.strictEqual(calls[0].arguments.length, 2);
37+
assert.strictEqual(typeof calls[0].arguments[0], 'number');
38+
assert.strictEqual(typeof calls[0].arguments[1], 'function');
39+
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
40+
done();
41+
}));
42+
}));
43+
});
44+
45+
test('does not perform flush', (t, done) => {
46+
const values = [undefined, null, false];
47+
const spy = t.mock.method(fs, 'fsync');
48+
let cnt = 0;
49+
50+
for (const flush of values) {
51+
const file = nextFile();
52+
const stream = fs.createWriteStream(file, { flush });
53+
54+
stream.write(data, common.mustSucceed(() => {
55+
stream.close(common.mustSucceed(() => {
56+
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
57+
cnt++;
58+
59+
if (cnt === values.length) {
60+
assert.strictEqual(spy.mock.calls.length, 0);
61+
done();
62+
}
63+
}));
64+
}));
65+
}
66+
});
67+
68+
test('works with file handles', async () => {
69+
const file = nextFile();
70+
const handle = await fsp.open(file, 'w');
71+
const stream = handle.createWriteStream({ flush: true });
72+
73+
return new Promise((resolve) => {
74+
stream.write(data, common.mustSucceed(() => {
75+
stream.close(common.mustSucceed(() => {
76+
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
77+
resolve();
78+
}));
79+
}));
80+
});
81+
});

0 commit comments

Comments
 (0)