From f6bb096b2b2f4e2917fcb71bc3a735fa8ce216b1 Mon Sep 17 00:00:00 2001 From: Sam Attard Date: Sat, 13 Jun 2026 03:15:17 +0000 Subject: [PATCH] fs: honor flush option in writeFileSync utf8 fast path The C++ utf8 fast path added in 4466deeb343 does not perform an fsync, but the gate routing writeFileSync through it did not consult the flush option added in e01c1d700d9. As a result writeFileSync(path, data, { encoding: 'utf8', flush: true }) wrote without flushing, while the same call without an explicit encoding flushed correctly. Skip the fast path when flush is requested. appendFileSync delegates to writeFileSync and was affected the same way. Extend the existing flush tests with the explicit-utf8-encoding case for both; the new cases fail without this change. Signed-off-by: Sam Attard --- lib/fs.js | 6 ++++-- test/parallel/test-fs-append-file-flush.js | 13 +++++++++++++ test/parallel/test-fs-write-file-flush.js | 13 +++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index db65f74c446174..3f5ac37af766d1 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -2844,8 +2844,10 @@ function writeFileSync(path, data, options) { const flag = options.flag || 'w'; - // C++ fast path for string data and UTF8 encoding - if (typeof data === 'string' && (options.encoding === 'utf8' || options.encoding === 'utf-8')) { + // C++ fast path for string data and UTF8 encoding. The fast path does + // not perform an fsync, so it cannot be used when flush is requested. + if (typeof data === 'string' && !flush && + (options.encoding === 'utf8' || options.encoding === 'utf-8')) { if (!isInt32(path)) { path = getValidatedPath(path); } diff --git a/test/parallel/test-fs-append-file-flush.js b/test/parallel/test-fs-append-file-flush.js index 69deeb4e8f9d14..cea779873b41ac 100644 --- a/test/parallel/test-fs-append-file-flush.js +++ b/test/parallel/test-fs-append-file-flush.js @@ -47,6 +47,19 @@ test('synchronous version', async (t) => { assert.strictEqual(spy.mock.calls.length, 0); }); + + await t.test('performs flush with explicit utf8 encoding', (t) => { + // Refs: the C++ utf8 fast path must not skip the flush. + const spy = t.mock.method(fs, 'fsyncSync'); + + for (const encoding of ['utf8', 'utf-8']) { + const file = nextFile(); + fs.appendFileSync(file, data, { encoding, flush: true }); + assert.strictEqual(fs.readFileSync(file, 'utf8'), data); + } + + assert.strictEqual(spy.mock.calls.length, 2); + }); }); test('callback version', async (t) => { diff --git a/test/parallel/test-fs-write-file-flush.js b/test/parallel/test-fs-write-file-flush.js index 98a8d637c5fa28..c259847b371a6f 100644 --- a/test/parallel/test-fs-write-file-flush.js +++ b/test/parallel/test-fs-write-file-flush.js @@ -47,6 +47,19 @@ test('synchronous version', async (t) => { assert.strictEqual(spy.mock.calls.length, 0); }); + + await t.test('performs flush with explicit utf8 encoding', (t) => { + // Refs: the C++ utf8 fast path must not skip the flush. + const spy = t.mock.method(fs, 'fsyncSync'); + + for (const encoding of ['utf8', 'utf-8']) { + const file = nextFile(); + fs.writeFileSync(file, data, { encoding, flush: true }); + assert.strictEqual(fs.readFileSync(file, 'utf8'), data); + } + + assert.strictEqual(spy.mock.calls.length, 2); + }); }); test('callback version', async (t) => {