| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 193091f commit 125c19d
28 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,7 +5,7 @@ const common = require('../common.js'); | |||
| 5 | 5 | const bench = common.createBenchmark(main, { | |
| 6 | 6 | consumers: [2, 8, 32], | |
| 7 | 7 | batches: [1e4], | |
| 8 | - backpressure: ['block'], | ||
| 8 | + backpressure: ['unbounded'], | ||
| 9 | 9 | n: [5], | |
| 10 | 10 | }, { | |
| 11 | 11 | flags: ['--experimental-stream-iter'], | |
@@ -24,7 +24,7 @@ async function main({ consumers, batches, backpressure, n }) { | |||
| 24 | 24 | ||
| 25 | 25 | bench.start(); | |
| 26 | 26 | for (let i = 0; i < n; i++) { | |
| 27 | - const shared = share(source(), { highWaterMark: 64, backpressure }); | ||
| 27 | + const shared = share(source(), { budget: 65536, backpressure }); | ||
| 28 | 28 | const readers = Array.from({ length: consumers }, () => array(shared.pull())); | |
| 29 | 29 | await Promise.all(readers); | |
| 30 | 30 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1276,7 +1276,7 @@ added: v23.8.0 | |||
| 1276 | 1276 | interleaved with data from other streams of the same priority level. | |
| 1277 | 1277 | When `false`, the stream should be completed before same-priority peers. | |
| 1278 | 1278 | **Default:** `false`. | |
| 1279 | - * `highWaterMark` {number} The maximum number of bytes that the writer | ||
| 1279 | + * `budget` {number} The maximum number of bytes that the writer | ||
| 1280 | 1280 | will buffer before `writeSync()` returns `false`. When the buffered | |
| 1281 | 1281 | data exceeds this limit, the caller should wait for drain before | |
| 1282 | 1282 | writing more. **Default:** `65536` (64 KB). | |
@@ -1317,7 +1317,7 @@ added: v23.8.0 | |||
| 1317 | 1317 | interleaved with data from other streams of the same priority level. | |
| 1318 | 1318 | When `false`, the stream should be completed before same-priority peers. | |
| 1319 | 1319 | **Default:** `false`. | |
| 1320 | - * `highWaterMark` {number} The maximum number of bytes that the writer | ||
| 1320 | + * `budget` {number} The maximum number of bytes that the writer | ||
| 1321 | 1321 | will buffer before `writeSync()` returns `false`. When the buffered | |
| 1322 | 1322 | data exceeds this limit, the caller should wait for drain before | |
| 1323 | 1323 | writing more. **Default:** `65536` (64 KB). | |
@@ -1924,7 +1924,7 @@ added: v23.8.0 | |||
| 1924 | 1924 | The directionality of the stream, or `null` if the stream has been destroyed | |
| 1925 | 1925 | or is still pending. Read only. | |
| 1926 | 1926 | ||
| 1927 | - ### `stream.highWaterMark` | ||
| 1927 | + ### `stream.budget` | ||
| 1928 | 1928 | ||
| 1929 | 1929 | <!-- YAML | |
| 1930 | 1930 | added: REPLACEME | |
@@ -2236,7 +2236,8 @@ The Writer has the following methods: | |||
| 2236 | 2236 | the QUIC transport-layer `INTERNAL_ERROR` (`0x1`) for raw QUIC). | |
| 2237 | 2237 | See [`stream.destroy()`][] for a full-stream abort that also resets | |
| 2238 | 2238 | the readable side via `STOP_SENDING`. | |
| 2239 | - * `desiredSize` — Available capacity in bytes, or `null` if closed/errored. | ||
| 2239 | + * `canWrite` — `true` if writes will be accepted, `false` if at capacity, | ||
| 2240 | + or `null` if closed/errored. | ||
| 2240 | 2241 | ||
| 2241 | 2242 | The bytes from each `writeSync()` / `writevSync()` / `write()` / `writev()` | |
| 2242 | 2243 | input chunk are copied into an internal buffer, so the caller's source | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -223,7 +223,7 @@ How each policy uses these buffers: | |||
| 223 | 223 | ||
| 224 | 224 | | Policy | Buffer limit | Pending writes limit | | |
| 225 | 225 | | --------------- | ------------ | -------------------- | | |
| 226 | - | `'strict'` | `budget` | `budget` | | ||
| 226 | + | `'strict'` | `budget` | 1 | | ||
| 227 | 227 | | `'unbounded'` | `budget` | Unbounded | | |
| 228 | 228 | | `'drop-oldest'` | `budget` | N/A (never waits) | | |
| 229 | 229 | | `'drop-newest'` | `budget` | N/A (never waits) | | |
@@ -232,8 +232,8 @@ How each policy uses these buffers: | |||
| 232 | 232 | ||
| 233 | 233 | Strict mode catches "fire-and-forget" patterns where the producer calls | |
| 234 | 234 | `write()` without awaiting, which would cause unbounded memory growth. | |
| 235 | - It limits both the buffer and the pending writes queue to | ||
| 236 | - `budget` bytes. | ||
| 235 | + It limits the buffer to `budget` bytes and the pending writes queue | ||
| 236 | + to a single entry. | ||
| 237 | 237 | ||
| 238 | 238 | If you properly await each write, you can only ever have one pending | |
| 239 | 239 | write at a time (yours), so you never hit the pending writes limit. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -297,8 +297,8 @@ const endpointRegistry = new SafeSet(); | |||
| 297 | 297 | * (e.g. HTTP/3). | |
| 298 | 298 | * @property {'high'|'default'|'low'} [priority] The priority level of the stream. | |
| 299 | 299 | * @property {boolean} [incremental] Whether to interleave data with same-priority streams. | |
| 300 | - * @property {number} [highWaterMark] The high water mark for write | ||
| 301 | - * backpressure, in bytes. **Default:** `65536`. | ||
| 300 | + * @property {number} [budget] The byte budget for write backpressure. | ||
| 301 | + * **Default:** `65536`. | ||
| 302 | 302 | * @property {OnHeadersCallback} [onheaders] Callback for incoming initial headers | |
| 303 | 303 | * @property {OnTrailersCallback} [ontrailers] Callback for incoming trailing headers | |
| 304 | 304 | * @property {OnInfoCallback} [oninfo] Callback for informational (1xx) headers | |
@@ -1325,7 +1325,7 @@ function applyCallbacks(session, cbs) { | |||
| 1325 | 1325 | * @param {QuicStream} stream The JS stream object | |
| 1326 | 1326 | * @param {any} body The body source | |
| 1327 | 1327 | */ | |
| 1328 | - const kDefaultHighWaterMark = 65536; | ||
| 1328 | + const kDefaultBudget = 65536; | ||
| 1329 | 1329 | const kDefaultMaxPendingDatagrams = 128; | |
| 1330 | 1330 | ||
| 1331 | 1331 | function configureOutbound(handle, stream, body) { | |
@@ -1405,20 +1405,20 @@ function configureOutbound(handle, stream, body) { | |||
| 1405 | 1405 | ); | |
| 1406 | 1406 | } | |
| 1407 | 1407 | ||
| 1408 | - // Sets the high water mark and initial writeDesiredSize for a streaming | ||
| 1408 | + // Sets the budget and initial writeDesiredSize for a streaming | ||
| 1409 | 1409 | // outbound source. Called after handle.initStreamingSource() for both | |
| 1410 | 1410 | // body-source and writer paths. One-shot body sources (string, Uint8Array, | |
| 1411 | 1411 | // Blob, FileHandle, etc.) do not use this -- they go through attachSource | |
| 1412 | 1412 | // and are not subject to backpressure. | |
| 1413 | 1413 | function initStreamingBackpressure(stream) { | |
| 1414 | 1414 | const state = getQuicStreamState(stream); | |
| 1415 | 1415 | // Only set defaults if the user hasn't already configured them | |
| 1416 | - // (e.g., via createBidirectionalStream({ highWaterMark: N })). | ||
| 1417 | - if (state.highWaterMark === 0) { | ||
| 1418 | - state.highWaterMark = kDefaultHighWaterMark; | ||
| 1416 | + // (e.g., via createBidirectionalStream({ budget: N })). | ||
| 1417 | + if (state.budget === 0) { | ||
| 1418 | + state.budget = kDefaultBudget; | ||
| 1419 | 1419 | } | |
| 1420 | 1420 | if (state.writeDesiredSize === 0) { | |
| 1421 | - state.writeDesiredSize = state.highWaterMark; | ||
| 1421 | + state.writeDesiredSize = state.budget; | ||
| 1422 | 1422 | } | |
| 1423 | 1423 | } | |
| 1424 | 1424 | ||
@@ -1699,23 +1699,23 @@ class QuicStream { | |||
| 1699 | 1699 | } | |
| 1700 | 1700 | ||
| 1701 | 1701 | /** | |
| 1702 | - * The high water mark for write backpressure. When the total queued | ||
| 1702 | + * The byte budget for write backpressure. When the total queued | ||
| 1703 | 1703 | * outbound bytes exceeds this value, writeSync returns false and | |
| 1704 | - * desiredSize drops to 0. Default is 65536 (64KB). | ||
| 1704 | + * canWrite returns false. Default is 65536 (64KB). | ||
| 1705 | 1705 | * @type {number} | |
| 1706 | 1706 | */ | |
| 1707 | - get highWaterMark() { | ||
| 1707 | + get budget() { | ||
| 1708 | 1708 | assertIsQuicStream(this); | |
| 1709 | - return this.#inner.state.highWaterMark; | ||
| 1709 | + return this.#inner.state.budget; | ||
| 1710 | 1710 | } | |
| 1711 | 1711 | ||
| 1712 | - set highWaterMark(val) { | ||
| 1712 | + set budget(val) { | ||
| 1713 | 1713 | assertIsQuicStream(this); | |
| 1714 | - validateInteger(val, 'highWaterMark', 0, 0xFFFFFFFF); | ||
| 1714 | + validateInteger(val, 'budget', 0, 0xFFFFFFFF); | ||
| 1715 | 1715 | const inner = this.#inner; | |
| 1716 | - inner.state.highWaterMark = val; | ||
| 1716 | + inner.state.budget = val; | ||
| 1717 | 1717 | // If writeDesiredSize hasn't been set yet (still 0 from initialization), | |
| 1718 | - // initialize it to the highWaterMark so the first write can proceed. | ||
| 1718 | + // initialize it to the budget so the first write can proceed. | ||
| 1719 | 1719 | if (inner.state.writeDesiredSize === 0 && val > 0) { | |
| 1720 | 1720 | inner.state.writeDesiredSize = val; | |
| 1721 | 1721 | } | |
@@ -2163,8 +2163,8 @@ class QuicStream { | |||
| 2163 | 2163 | // will accept the data into the DataQueue and | |
| 2164 | 2164 | // UpdateWriteDesiredSize() will drop writeDesiredSize toward 0, | |
| 2165 | 2165 | // at which point the standard drain mechanism takes over. | |
| 2166 | - // This follows the Web Streams model where writes beyond the HWM | ||
| 2167 | - // succeed and backpressure applies to *subsequent* writes. | ||
| 2166 | + // This follows the iter-streams model where writes beyond the | ||
| 2167 | + // budget succeed and backpressure applies to *subsequent* writes. | ||
| 2168 | 2168 | if (stream.#inner.state.writeDesiredSize === 0) return false; | |
| 2169 | 2169 | const result = handle.write([chunk]); | |
| 2170 | 2170 | if (result === undefined) return false; | |
@@ -2323,9 +2323,9 @@ class QuicStream { | |||
| 2323 | 2323 | ||
| 2324 | 2324 | const writer = { | |
| 2325 | 2325 | __proto__: null, | |
| 2326 | - get desiredSize() { | ||
| 2326 | + get canWrite() { | ||
| 2327 | 2327 | if (closed || errored || stream.#inner.state.writeEnded) return null; | |
| 2328 | - return stream.#inner.state.writeDesiredSize; | ||
| 2328 | + return stream.#inner.state.writeDesiredSize > 0; | ||
| 2329 | 2329 | }, | |
| 2330 | 2330 | writeSync, | |
| 2331 | 2331 | write, | |
@@ -3254,7 +3254,7 @@ class QuicSession { | |||
| 3254 | 3254 | body, | |
| 3255 | 3255 | priority = 'default', | |
| 3256 | 3256 | incremental = false, | |
| 3257 | - highWaterMark = kDefaultHighWaterMark, | ||
| 3257 | + budget = kDefaultBudget, | ||
| 3258 | 3258 | headers, | |
| 3259 | 3259 | onheaders, | |
| 3260 | 3260 | ontrailers, | |
@@ -3290,8 +3290,8 @@ class QuicSession { | |||
| 3290 | 3290 | stream[kAttachFileHandle](body); | |
| 3291 | 3291 | } | |
| 3292 | 3292 | ||
| 3293 | - // Set the high water mark for backpressure. | ||
| 3294 | - stream.highWaterMark = highWaterMark; | ||
| 3293 | + // Set the byte budget for backpressure. | ||
| 3294 | + stream.budget = budget; | ||
| 3295 | 3295 | ||
| 3296 | 3296 | // Set stream callbacks before sending headers to avoid missing events. | |
| 3297 | 3297 | if (onheaders) stream.onheaders = onheaders; | |
@@ -4047,8 +4047,8 @@ class QuicSession { | |||
| 4047 | 4047 | const stream = new QuicStream(kPrivateConstructor, handle, this, direction, | |
| 4048 | 4048 | false /* isLocal */); | |
| 4049 | 4049 | ||
| 4050 | - // Set the default high water mark for received streams. | ||
| 4051 | - stream.highWaterMark = kDefaultHighWaterMark; | ||
| 4050 | + // Set the default byte budget for received streams. | ||
| 4051 | + stream.budget = kDefaultBudget; | ||
| 4052 | 4052 | ||
| 4053 | 4053 | // A new stream was received. If we don't have an onstream callback, then | |
| 4054 | 4054 | // there's nothing we can do about it. Destroy the stream in this case. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -104,7 +104,7 @@ const { | |||
| 104 | 104 | IDX_STATE_STREAM_WANTS_TRAILERS, | |
| 105 | 105 | IDX_STATE_STREAM_RECEIVED_EARLY_DATA, | |
| 106 | 106 | IDX_STATE_STREAM_WRITE_DESIRED_SIZE, | |
| 107 | - IDX_STATE_STREAM_HIGH_WATER_MARK, | ||
| 107 | + IDX_STATE_STREAM_BUDGET, | ||
| 108 | 108 | IDX_STATE_STREAM_RESET_CODE, | |
| 109 | 109 | } = internalBinding('quic'); | |
| 110 | 110 | ||
@@ -871,18 +871,18 @@ class QuicStreamState { | |||
| 871 | 871 | } | |
| 872 | 872 | ||
| 873 | 873 | /** @type {number} */ | |
| 874 | - get highWaterMark() { | ||
| 874 | + get budget() { | ||
| 875 | 875 | const handle = this.#handle; | |
| 876 | 876 | if (handle === undefined) return undefined; | |
| 877 | 877 | return DataViewPrototypeGetUint32( | |
| 878 | - handle, this.#offset + IDX_STATE_STREAM_HIGH_WATER_MARK, kIsLittleEndian); | ||
| 878 | + handle, this.#offset + IDX_STATE_STREAM_BUDGET, kIsLittleEndian); | ||
| 879 | 879 | } | |
| 880 | 880 | ||
| 881 | - set highWaterMark(val) { | ||
| 881 | + set budget(val) { | ||
| 882 | 882 | const handle = this.#handle; | |
| 883 | 883 | if (handle === undefined) return; | |
| 884 | 884 | DataViewPrototypeSetUint32( | |
| 885 | - handle, this.#offset + IDX_STATE_STREAM_HIGH_WATER_MARK, val, kIsLittleEndian); | ||
| 885 | + handle, this.#offset + IDX_STATE_STREAM_BUDGET, val, kIsLittleEndian); | ||
| 886 | 886 | } | |
| 887 | 887 | ||
| 888 | 888 | toString() { | |
@@ -908,7 +908,7 @@ class QuicStreamState { | |||
| 908 | 908 | early, | |
| 909 | 909 | resetCode, | |
| 910 | 910 | writeDesiredSize, | |
| 911 | - highWaterMark, | ||
| 911 | + budget, | ||
| 912 | 912 | } = this; | |
| 913 | 913 | return { | |
| 914 | 914 | __proto__: null, | |
@@ -928,7 +928,7 @@ class QuicStreamState { | |||
| 928 | 928 | early, | |
| 929 | 929 | resetCode, | |
| 930 | 930 | writeDesiredSize, | |
| 931 | - highWaterMark, | ||
| 931 | + budget, | ||
| 932 | 932 | }; | |
| 933 | 933 | } | |
| 934 | 934 | ||
@@ -965,7 +965,7 @@ class QuicStreamState { | |||
| 965 | 965 | early, | |
| 966 | 966 | resetCode, | |
| 967 | 967 | writeDesiredSize, | |
| 968 | - highWaterMark, | ||
| 968 | + budget, | ||
| 969 | 969 | } = this; | |
| 970 | 970 | ||
| 971 | 971 | return `QuicStreamState ${inspect({ | |
@@ -985,7 +985,7 @@ class QuicStreamState { | |||
| 985 | 985 | early, | |
| 986 | 986 | resetCode, | |
| 987 | 987 | writeDesiredSize, | |
| 988 | - highWaterMark, | ||
| 988 | + budget, | ||
| 989 | 989 | }, opts)}`; | |
| 990 | 990 | } | |
| 991 | 991 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -55,7 +55,6 @@ const { | |||
| 55 | 55 | const { | |
| 56 | 56 | kMultiConsumerDefaultBudget, | |
| 57 | 57 | kResolvedPromise, | |
| 58 | - clampBudget, | ||
| 59 | 58 | convertChunks, | |
| 60 | 59 | getWriterSignal, | |
| 61 | 60 | getMinCursor, | |
@@ -779,7 +778,7 @@ function broadcast(options = { __proto__: null }) { | |||
| 779 | 778 | ||
| 780 | 779 | const opts = { | |
| 781 | 780 | __proto__: null, | |
| 782 | - budget: clampBudget(budget), | ||
| 781 | + budget, | ||
| 783 | 782 | backpressure, | |
| 784 | 783 | signal, | |
| 785 | 784 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -37,7 +37,6 @@ const { | |||
| 37 | 37 | const { | |
| 38 | 38 | kPushDefaultBudget, | |
| 39 | 39 | kResolvedPromise, | |
| 40 | - clampBudget, | ||
| 41 | 40 | onSignalAbort, | |
| 42 | 41 | toUint8Array, | |
| 43 | 42 | convertChunks, | |
@@ -99,7 +98,7 @@ class PushQueue { | |||
| 99 | 98 | if (signal !== undefined) { | |
| 100 | 99 | validateAbortSignal(signal, 'options.signal'); | |
| 101 | 100 | } | |
| 102 | - this.#budget = clampBudget(budget); | ||
| 101 | + this.#budget = budget; | ||
| 103 | 102 | this.#backpressure = backpressure; | |
| 104 | 103 | this.#signal = signal; | |
| 105 | 104 | this.#abortHandler = undefined; | |
@@ -127,7 +126,12 @@ class PushQueue { | |||
| 127 | 126 | if (this.#writerState !== 'open' || this.#consumerState !== 'active') { | |
| 128 | 127 | return null; | |
| 129 | 128 | } | |
| 130 | - return this.#bufferedBytes < this.#budget; | ||
| 129 | + if ((this.#backpressure === 'strict' || | ||
| 130 | + this.#backpressure === 'unbounded') && | ||
| 131 | + this.#bufferedBytes >= this.#budget) { | ||
| 132 | + return false; | ||
| 133 | + } | ||
| 134 | + return true; | ||
| 131 | 135 | } | |
| 132 | 136 | ||
| 133 | 137 | /** | |
@@ -156,6 +160,10 @@ class PushQueue { | |||
| 156 | 160 | ||
| 157 | 161 | const batchSize = this.#batchByteSize(chunks); | |
| 158 | 162 | ||
| 163 | + // Skip empty chunks -- zero-byte writes would accumulate infinitely | ||
| 164 | + // without ever triggering backpressure under a byte-budget model. | ||
| 165 | + if (batchSize === 0) return true; | ||
| 166 | + | ||
| 159 | 167 | if (this.#bufferedBytes >= this.#budget) { | |
| 160 | 168 | switch (this.#backpressure) { | |
| 161 | 169 | case 'strict': | |
@@ -181,6 +189,11 @@ class PushQueue { | |||
| 181 | 189 | this.#bytesWritten += batchSize; | |
| 182 | 190 | ||
| 183 | 191 | this.#resolvePendingReads(); | |
| 192 | + // After drop-oldest, evicting a large chunk may bring us under budget. | ||
| 193 | + // Resolve pending drains so writers waiting on backpressure can proceed. | ||
| 194 | + if (this.#bufferedBytes < this.#budget) { | ||
| 195 | + this.#resolvePendingDrains(true); | ||
| 196 | + } | ||
| 184 | 197 | return true; | |
| 185 | 198 | } | |
| 186 | 199 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,7 +36,6 @@ const { | |||
| 36 | 36 | ||
| 37 | 37 | const { | |
| 38 | 38 | kMultiConsumerDefaultBudget, | |
| 39 | - clampBudget, | ||
| 40 | 39 | getMinCursor, | |
| 41 | 40 | hasProtocol, | |
| 42 | 41 | onSignalAbort, | |
@@ -696,7 +695,7 @@ function share(source, options = { __proto__: null }) { | |||
| 696 | 695 | ||
| 697 | 696 | const opts = { | |
| 698 | 697 | __proto__: null, | |
| 699 | - budget: clampBudget(budget), | ||
| 698 | + budget, | ||
| 700 | 699 | backpressure, | |
| 701 | 700 | signal, | |
| 702 | 701 | }; | |
@@ -723,7 +722,7 @@ function shareSync(source, options = { __proto__: null }) { | |||
| 723 | 722 | ||
| 724 | 723 | const opts = { | |
| 725 | 724 | __proto__: null, | |
| 726 | - budget: clampBudget(budget), | ||
| 725 | + budget, | ||
| 727 | 726 | backpressure, | |
| 728 | 727 | }; | |
| 729 | 728 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments