Commit 537a20c1135 for nodejs
commit 537a20c113528da44323edf78deb83cc93c3a30a
Author: Hamza Kargin <hamzakargin@outlook.com>
Date: Sun Nov 15 13:43:51 2026 +0400
fs: respect setDefaultHighWaterMark in ReadStream
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 <hamzakargin@outlook.com>
PR-URL: https://github.com/nodejs/node/pull/66956
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
diff --git a/doc/api/fs.md b/doc/api/fs.md
index 93e6e7c68a1..929ea302957 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 5fd47bb5e49..9bfbaa01125 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 46e0ff9c955..c68dba2a4d8 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 00000000000..3ec78dacc43
--- /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());