GeezerVison is showing github.com — fs: add flush option to createWriteStream() · nodejs/node@d1aa62f
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/fs.md +11Lines changed: 11 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -318,6 +318,10 @@ fd.createReadStream({ start: 90, end: 99 }); | 318 | 318 |
| 319 | 319 | <!-- YAML | 320 | 320 | 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. | 321 | 325 | --> | 322 | 326 |
| 323 | 327 | * `options` {Object} | @@ -326,6 +330,8 @@ added: v16.11.0 | 326 | 330 | * `emitClose` {boolean} **Default:** `true` | 327 | 331 | * `start` {integer} | 328 | 332 | * `highWaterMark` {number} **Default:** `16384` | | 333 | + * `flush` {boolean} If `true`, the underlying file descriptor is flushed | | 334 | + prior to closing it. **Default:** `false`. | 329 | 335 | * Returns: {fs.WriteStream} | 330 | 336 |
| 331 | 337 | `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. | 2514 | 2520 | <!-- YAML | 2515 | 2521 | added: v0.1.31 | 2516 | 2522 | changes: | | 2523 | + - version: REPLACEME | | 2524 | + pr-url: https://github.com/nodejs/node/pull/50093 | | 2525 | + description: The `flush` option is now supported. | 2517 | 2526 | - version: v16.10.0 | 2518 | 2527 | pr-url: https://github.com/nodejs/node/pull/40013 | 2519 | 2528 | description: The `fs` option does not need `open` method if an `fd` was provided. | @@ -2567,6 +2576,8 @@ changes: | 2567 | 2576 | * `fs` {Object|null} **Default:** `null` | 2568 | 2577 | * `signal` {AbortSignal|null} **Default:** `null` | 2569 | 2578 | * `highWaterMark` {number} **Default:** `16384` | | 2579 | + * `flush` {boolean} If `true`, the underlying file descriptor is flushed | | 2580 | + prior to closing it. **Default:** `false`. | 2570 | 2581 | * Returns: {fs.WriteStream} | 2571 | 2582 |
| 2572 | 2583 | `options` may also include a `start` option to allow writing data at some | |
Collapse file Copy file name to clipboardExpand all lines: lib/fs.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -3106,6 +3106,7 @@ function createReadStream(path, options) { | 3106 | 3106 | * emitClose?: boolean; | 3107 | 3107 | * start: number; | 3108 | 3108 | * fs?: object | null; | | 3109 | + * flush?: boolean; | 3109 | 3110 | * }} [options] | 3110 | 3111 | * @returns {WriteStream} | 3111 | 3112 | */ | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/fs/promises.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -349,6 +349,7 @@ class FileHandle extends EventEmitterMixin(JSTransferable) { | 349 | 349 | * autoClose?: boolean; | 350 | 350 | * emitClose?: boolean; | 351 | 351 | * start: number; | | 352 | + * flush?: boolean; | 352 | 353 | * }} [options] | 353 | 354 | * @returns {WriteStream} | 354 | 355 | */ | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/fs/streams.js +24-4Lines changed: 24 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -23,6 +23,7 @@ const { | 23 | 23 | kEmptyObject, | 24 | 24 | } = require('internal/util'); | 25 | 25 | const { | | 26 | + validateBoolean, | 26 | 27 | validateFunction, | 27 | 28 | validateInteger, | 28 | 29 | } = require('internal/validators'); | @@ -92,6 +93,9 @@ const FileHandleOperations = (handle) => { | 92 | 93 | PromisePrototypeThen(handle.close(), | 93 | 94 | () => cb(), cb); | 94 | 95 | }, | | 96 | + fsync: (fd, cb) => { | | 97 | + PromisePrototypeThen(handle.sync(), () => cb(), cb); | | 98 | + }, | 95 | 99 | read: (fd, buf, offset, length, pos, cb) => { | 96 | 100 | PromisePrototypeThen(handle.read(buf, offset, length, pos), | 97 | 101 | (r) => cb(null, r.bytesRead, r.buffer), | @@ -113,14 +117,22 @@ const FileHandleOperations = (handle) => { | 113 | 117 | function close(stream, err, cb) { | 114 | 118 | if (!stream.fd) { | 115 | 119 | 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); | 119 | 123 | }); | 120 | | - stream.fd = null; | | 124 | + } else { | | 125 | + _close(stream, err, cb); | 121 | 126 | } | 122 | 127 | } | 123 | 128 |
| | 129 | +function _close(stream, err, cb) { | | 130 | + stream[kFs].close(stream.fd, (er) => { | | 131 | + cb(er || err); | | 132 | + }); | | 133 | + stream.fd = null; | | 134 | +} | | 135 | + | 124 | 136 | function importFd(stream, options) { | 125 | 137 | if (typeof options.fd === 'number') { | 126 | 138 | // When fd is a raw descriptor, we must keep our fingers crossed | @@ -350,6 +362,14 @@ function WriteStream(path, options) { | 350 | 362 | validateFunction(this[kFs].close, 'options.fs.close'); | 351 | 363 | } | 352 | 364 |
| | 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 | + | 353 | 373 | // It's enough to override either, in which case only one will be used. | 354 | 374 | if (!this[kFs].write) { | 355 | 375 | this._write = null; | |
Collapse file Copy file name to clipboard +81Lines changed: 81 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +}); |
|