From dfa7cb144dd429f8395ea49b49c2b897b5f76639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hamza=20Karg=C4=B1n?= Date: Fri, 23 Oct 2026 15:31:37 +0400 Subject: [PATCH] fs: respect setDefaultHighWaterMark in ReadStream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReadStream ignored stream.setDefaultHighWaterMark() and always used 64 KiB. Use the user-set value when there is one, and keep 64 KiB otherwise so Windows does not drop to the 16 KiB stream default. Fixes: https://github.com/nodejs/node/issues/66937 Signed-off-by: Hamza Kargın --- doc/api/fs.md | 10 ++++-- lib/internal/fs/streams.js | 3 +- lib/internal/streams/state.js | 7 ++++ .../test-fs-read-stream-set-default-hwm.js | 34 +++++++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 test/parallel/test-fs-read-stream-set-default-hwm.js diff --git a/doc/api/fs.md b/doc/api/fs.md index 2c5690b5f345..ef685d3f854e 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -265,7 +265,8 @@ added: v16.11.0 * `emitClose` {boolean} **Default:** `true` * `start` {integer} * `end` {integer} **Default:** `Infinity` - * `highWaterMark` {integer} **Default:** `64 * 1024` + * `highWaterMark` {integer} **Default:** `64 * 1024`, or the value set with + [`stream.setDefaultHighWaterMark()`][] * `signal` {AbortSignal|undefined} **Default:** `undefined` * Returns: {fs.ReadStream} @@ -781,7 +782,8 @@ added: v18.11.0 * `emitClose` {boolean} **Default:** `true` * `start` {integer} * `end` {integer} **Default:** `Infinity` - * `highWaterMark` {integer} **Default:** `64 * 1024` + * `highWaterMark` {integer} **Default:** `64 * 1024`, or the value set with + [`stream.setDefaultHighWaterMark()`][] * Returns: {readline.InterfaceConstructor} Convenience method to create a `readline` interface and stream over the file. @@ -3104,7 +3106,8 @@ changes: * `emitClose` {boolean} **Default:** `true` * `start` {integer} * `end` {integer} **Default:** `Infinity` - * `highWaterMark` {integer} **Default:** `64 * 1024` + * `highWaterMark` {integer} **Default:** `64 * 1024`, or the value set with + [`stream.setDefaultHighWaterMark()`][] * `fs` {Object|null} **Default:** `null` * `signal` {AbortSignal|null} **Default:** `null` * `windowsHandle` {bigint} A raw Win32 `HANDLE` value to read from, in place @@ -9787,6 +9790,7 @@ the file contents. [`path.matchesGlob()`]: path.md#pathmatchesglobpath-pattern [`statfs.bsize`]: #statfsbsize [`stream.getDefaultHighWaterMark()`]: stream.md#streamgetdefaulthighwatermarkobjectmode +[`stream.setDefaultHighWaterMark()`]: stream.md#streamsetdefaulthighwatermarkobjectmode-value [`stream/iter pipeTo()`]: stream_iter.md#pipetosource-transforms-writer-options [`stream/iter pull()`]: stream_iter.md#pullsource-transforms-options [`stream/iter pullSync()`]: stream_iter.md#pullsyncsource-transforms diff --git a/lib/internal/fs/streams.js b/lib/internal/fs/streams.js index c8c86b59640a..3290f885741d 100644 --- a/lib/internal/fs/streams.js +++ b/lib/internal/fs/streams.js @@ -33,6 +33,7 @@ const { validateThisInternalField, } = require('internal/validators'); const { errorOrDestroy } = require('internal/streams/destroy'); +const { getUserDefaultHighWaterMark } = require('internal/streams/state'); const fs = require('fs'); const { kRef, kUnref, FileHandle } = require('internal/fs/promises'); const { Buffer } = require('buffer'); @@ -193,7 +194,7 @@ function ReadStream(path, options) { // A little bit bigger buffer and water marks by default options = copyObject(getOptions(options, kEmptyObject)); if (options.highWaterMark === undefined) - options.highWaterMark = 64 * 1024; + options.highWaterMark = getUserDefaultHighWaterMark() ?? 64 * 1024; if (options.autoDestroy === undefined) { options.autoDestroy = false; diff --git a/lib/internal/streams/state.js b/lib/internal/streams/state.js index 46e0ff9c9553..c68dba2a4d88 100644 --- a/lib/internal/streams/state.js +++ b/lib/internal/streams/state.js @@ -11,6 +11,7 @@ const { ERR_INVALID_ARG_VALUE } = require('internal/errors').codes; // TODO (fix): For some reason Windows CI fails with bigger hwm. let defaultHighWaterMarkBytes = process.platform === 'win32' ? 16 * 1024 : 64 * 1024; let defaultHighWaterMarkObjectMode = 16; +let defaultHighWaterMarkBytesSet = false; function highWaterMarkFrom(options, isDuplex, duplexKey) { return options.highWaterMark != null ? options.highWaterMark : @@ -27,9 +28,14 @@ function setDefaultHighWaterMark(objectMode, value) { defaultHighWaterMarkObjectMode = value; } else { defaultHighWaterMarkBytes = value; + defaultHighWaterMarkBytesSet = true; } } +function getUserDefaultHighWaterMark() { + return defaultHighWaterMarkBytesSet ? defaultHighWaterMarkBytes : undefined; +} + function getHighWaterMark(state, options, duplexKey, isDuplex) { const hwm = highWaterMarkFrom(options, isDuplex, duplexKey); if (hwm != null) { @@ -47,5 +53,6 @@ function getHighWaterMark(state, options, duplexKey, isDuplex) { module.exports = { getHighWaterMark, getDefaultHighWaterMark, + getUserDefaultHighWaterMark, setDefaultHighWaterMark, }; diff --git a/test/parallel/test-fs-read-stream-set-default-hwm.js b/test/parallel/test-fs-read-stream-set-default-hwm.js new file mode 100644 index 000000000000..3ec78dacc43a --- /dev/null +++ b/test/parallel/test-fs-read-stream-set-default-hwm.js @@ -0,0 +1,34 @@ +'use strict'; + +const common = require('../common'); + +const assert = require('node:assert'); +const fs = require('node:fs'); +const { setDefaultHighWaterMark } = require('node:stream'); + +{ + const stream = fs.createReadStream(__filename); + assert.strictEqual(stream.readableHighWaterMark, 64 * 1024); + stream.destroy(); +} + +setDefaultHighWaterMark(false, 1024 * 1024); + +{ + const stream = fs.createReadStream(__filename); + assert.strictEqual(stream.readableHighWaterMark, 1024 * 1024); + stream.destroy(); +} + +{ + const stream = fs.createReadStream(__filename, { highWaterMark: 1024 }); + assert.strictEqual(stream.readableHighWaterMark, 1024); + stream.destroy(); +} + +fs.promises.open(__filename).then(async (fh) => { + const stream = fh.createReadStream({ autoClose: false }); + assert.strictEqual(stream.readableHighWaterMark, 1024 * 1024); + stream.destroy(); + await fh.close(); +}).then(common.mustCall());