Skip to content

fs: flush option ignored when writing/appending strings #60552

@ChALkeR

Description

@ChALkeR

#50009 and #49884 conflict and were merged independently

This was exposed in #60539 CI run

await t.test('performs flush', (t) => {
const spy = t.mock.method(fs, 'fsyncSync');
const file = nextFile();
fs.writeFileSync(file, data, { flush: true });
const calls = spy.mock.calls;
assert.strictEqual(calls.length, 1);
assert.strictEqual(calls[0].result, undefined);
assert.strictEqual(calls[0].error, undefined);
assert.strictEqual(calls[0].arguments.length, 1);
assert.strictEqual(typeof calls[0].arguments[0], 'number');
assert.strictEqual(fs.readFileSync(file, 'utf8'), data);
});
fails if encoding is specified:

diff --git a/test/parallel/test-fs-write-file-flush.js b/test/parallel/test-fs-write-file-flush.js
index 98a8d637c5f..c28e536c528 100644
--- a/test/parallel/test-fs-write-file-flush.js
+++ b/test/parallel/test-fs-write-file-flush.js
@@ -26,7 +26,7 @@ test('synchronous version', async (t) => {
   await t.test('performs flush', (t) => {
     const spy = t.mock.method(fs, 'fsyncSync');
     const file = nextFile();
-    fs.writeFileSync(file, data, { flush: true });
+    fs.writeFileSync(file, data, { flush: true, encoding: 'utf8' });
     const calls = spy.mock.calls;
     assert.strictEqual(calls.length, 1);
     assert.strictEqual(calls[0].result, undefined);

Here, if options are set to e.g. { flush: true } (or any other object), they do not default to those values

node/lib/fs.js

Lines 2322 to 2327 in dbe45b7

options = getOptions(options, {
encoding: 'utf8',
mode: 0o666,
flag: 'w',
flush: false,
});

As getOptions does not merge options with the default ones, unless it's just an encoding string:

function getOptions(options, defaultOptions = kEmptyObject) {
if (options == null || typeof options === 'function') {
return defaultOptions;
}
if (typeof options === 'string') {
defaultOptions = { ...defaultOptions };
defaultOptions.encoding = options;
options = defaultOptions;
} else if (typeof options !== 'object') {
throw new ERR_INVALID_ARG_TYPE('options', ['string', 'Object'], options);
}
if (options.encoding !== 'buffer')
assertEncoding(options.encoding);
if (options.signal !== undefined) {
validateAbortSignal(options.signal, 'options.signal');
}
return options;
}

And the options.encoding === 'utf8' codepath was not called when options were set but options.encoding was undefined, as in flush testcases, resulting in the slow path

The bug was still triggered if both encoding and flush were set though

#60539 fixes that accidental slow path when options object without encoding is passed
But it exposes the flush bug even when encoding is not set

And fast path doesn't handle options.flush at all

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions