| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 67688cc commit cbb2568
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -109,9 +109,11 @@ const { | |||
| 109 | 109 | extractSizeAlgorithm, | |
| 110 | 110 | getNonWritablePropertyDescriptor, | |
| 111 | 111 | isBrandCheck, | |
| 112 | + kEmptyQueue, | ||
| 112 | 113 | kState, | |
| 113 | 114 | kType, | |
| 114 | 115 | lazyTransfer, | |
| 116 | + materializeQueue, | ||
| 115 | 117 | nonOpCancel, | |
| 116 | 118 | nonOpPull, | |
| 117 | 119 | nonOpStart, | |
@@ -2519,6 +2521,11 @@ function readableStreamDefaultControllerEnqueue(controller, chunk) { | |||
| 2519 | 2521 | reader[kType] === 'ReadableStreamDefaultReader' && | |
| 2520 | 2522 | reader[kState].readRequests.length) { | |
| 2521 | 2523 | readableStreamFulfillReadRequest(stream, chunk, false); | |
| 2524 | + } else if (controllerState.sizeAlgorithm === defaultSizeAlgorithm) { | ||
| 2525 | + // The internal default size algorithm is never observable by user | ||
| 2526 | + // code, always returns 1, and cannot throw: enqueue with the | ||
| 2527 | + // constant instead of calling it. | ||
| 2528 | + enqueueValueWithSize(controller, chunk, 1); | ||
| 2522 | 2529 | } else { | |
| 2523 | 2530 | try { | |
| 2524 | 2531 | const chunkSize = | |
@@ -2676,7 +2683,7 @@ function setupReadableStreamDefaultController( | |||
| 2676 | 2683 | pulling: false, | |
| 2677 | 2684 | pullFulfilled: undefined, | |
| 2678 | 2685 | pullRejected: undefined, | |
| 2679 | - queue: [], | ||
| 2686 | + queue: kEmptyQueue, | ||
| 2680 | 2687 | queueTotalSize: 0, | |
| 2681 | 2688 | started: false, | |
| 2682 | 2689 | sizeAlgorithm, | |
@@ -3151,14 +3158,13 @@ function readableByteStreamControllerEnqueueChunkToQueue( | |||
| 3151 | 3158 | buffer, | |
| 3152 | 3159 | byteOffset, | |
| 3153 | 3160 | byteLength) { | |
| 3154 | - ArrayPrototypePush( | ||
| 3155 | - controller[kState].queue, | ||
| 3156 | - { | ||
| 3157 | - buffer, | ||
| 3158 | - byteOffset, | ||
| 3159 | - byteLength, | ||
| 3160 | - }); | ||
| 3161 | - controller[kState].queueTotalSize += byteLength; | ||
| 3161 | + const state = controller[kState]; | ||
| 3162 | + materializeQueue(state).push({ | ||
| 3163 | + buffer, | ||
| 3164 | + byteOffset, | ||
| 3165 | + byteLength, | ||
| 3166 | + }); | ||
| 3167 | + state.queueTotalSize += byteLength; | ||
| 3162 | 3168 | } | |
| 3163 | 3169 | ||
| 3164 | 3170 | function readableByteStreamControllerEnqueueDetachedPullIntoToQueue( | |
@@ -3213,7 +3219,7 @@ function readableByteStreamControllerFillPullIntoDescriptorFromQueue( | |||
| 3213 | 3219 | } = controller[kState]; | |
| 3214 | 3220 | ||
| 3215 | 3221 | while (totalBytesToCopyRemaining) { | |
| 3216 | - const headOfQueue = queue[0]; | ||
| 3222 | + const headOfQueue = queue.peek(); | ||
| 3217 | 3223 | const bytesToCopy = MathMin( | |
| 3218 | 3224 | totalBytesToCopyRemaining, | |
| 3219 | 3225 | headOfQueue.byteLength); | |
@@ -3231,7 +3237,7 @@ function readableByteStreamControllerFillPullIntoDescriptorFromQueue( | |||
| 3231 | 3237 | headOfQueue.byteOffset, | |
| 3232 | 3238 | bytesToCopy); | |
| 3233 | 3239 | if (headOfQueue.byteLength === bytesToCopy) { | |
| 3234 | - ArrayPrototypeShift(queue); | ||
| 3240 | + queue.shift(); | ||
| 3235 | 3241 | } else { | |
| 3236 | 3242 | headOfQueue.byteOffset += bytesToCopy; | |
| 3237 | 3243 | headOfQueue.byteLength -= bytesToCopy; | |
@@ -3447,7 +3453,7 @@ function readableByteStreamControllerDequeueChunk(controller) { | |||
| 3447 | 3453 | buffer, | |
| 3448 | 3454 | byteOffset, | |
| 3449 | 3455 | byteLength, | |
| 3450 | - } = ArrayPrototypeShift(controller[kState].queue); | ||
| 3456 | + } = controller[kState].queue.shift(); | ||
| 3451 | 3457 | ||
| 3452 | 3458 | controller[kState].queueTotalSize -= byteLength; | |
| 3453 | 3459 | readableByteStreamControllerHandleQueueDrain(controller); | |
@@ -3543,7 +3549,7 @@ function setupReadableByteStreamController( | |||
| 3543 | 3549 | pullRejected: undefined, | |
| 3544 | 3550 | started: false, | |
| 3545 | 3551 | stream, | |
| 3546 | - queue: [], | ||
| 3552 | + queue: kEmptyQueue, | ||
| 3547 | 3553 | queueTotalSize: 0, | |
| 3548 | 3554 | highWaterMark, | |
| 3549 | 3555 | pullAlgorithm, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,18 +1,18 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | + Array, | ||
| 4 | 5 | ArrayBufferPrototypeGetByteLength, | |
| 5 | 6 | ArrayBufferPrototypeGetDetached, | |
| 6 | 7 | ArrayBufferPrototypeSlice, | |
| 7 | - ArrayPrototypePush, | ||
| 8 | - ArrayPrototypeShift, | ||
| 9 | 8 | AsyncIteratorPrototype, | |
| 10 | 9 | DataViewPrototypeGetBuffer, | |
| 11 | 10 | DataViewPrototypeGetByteLength, | |
| 12 | 11 | DataViewPrototypeGetByteOffset, | |
| 13 | 12 | FunctionPrototypeCall, | |
| 14 | 13 | MathMax, | |
| 15 | 14 | NumberIsNaN, | |
| 15 | + ObjectFreeze, | ||
| 16 | 16 | PromisePrototypeThen, | |
| 17 | 17 | PromiseReject, | |
| 18 | 18 | PromiseResolve, | |
@@ -152,32 +152,167 @@ function isBrandCheck(brand) { | |||
| 152 | 152 | }; | |
| 153 | 153 | } | |
| 154 | 154 | ||
| 155 | + // Backing store for the spec's [[queue]]: a power-of-two ring buffer | ||
| 156 | + // instead of a plain array. Entries are pushed at the tail and consumed | ||
| 157 | + // at the head, so a plain array either moves every element or forces the | ||
| 158 | + // engine to re-linearize on each shift, and the default controllers would | ||
| 159 | + // additionally have to allocate a { value, size } wrapper object per | ||
| 160 | + // chunk just to keep the pair together. Default readable/writable | ||
| 161 | + // controller queues store each entry as (value, size) in two consecutive | ||
| 162 | + // slots via the *Pair methods; the readable byte controller queue stores | ||
| 163 | + // its chunk descriptor records in single slots via push/shift/peek. A | ||
| 164 | + // given instance only ever uses one of the two access patterns, so | ||
| 165 | + // head/tail stay aligned to the entry stride. | ||
| 166 | + class Queue { | ||
| 167 | + constructor(listLength = 8) { | ||
| 168 | + this.head = 0; | ||
| 169 | + this.tail = 0; | ||
| 170 | + // Number of logical entries currently in the queue: (value, size) | ||
| 171 | + // pairs for default controller queues, descriptor records for byte | ||
| 172 | + // controller queues. | ||
| 173 | + this.length = 0; | ||
| 174 | + this.capacityMask = listLength - 1; | ||
| 175 | + this.list = new Array(listLength); | ||
| 176 | + this.dequeuedSize = 0; | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + // Single-slot entries (readable byte controller chunk records). | ||
| 180 | + | ||
| 181 | + push(entry) { | ||
| 182 | + const tail = this.tail; | ||
| 183 | + this.list[tail] = entry; | ||
| 184 | + this.tail = (tail + 1) & this.capacityMask; | ||
| 185 | + this.length++; | ||
| 186 | + if (this.tail === this.head) | ||
| 187 | + this.grow(); | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + shift() { | ||
| 191 | + const head = this.head; | ||
| 192 | + const list = this.list; | ||
| 193 | + const entry = list[head]; | ||
| 194 | + list[head] = undefined; | ||
| 195 | + this.head = (head + 1) & this.capacityMask; | ||
| 196 | + if (--this.length === 0) | ||
| 197 | + this.rewind(); | ||
| 198 | + return entry; | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + peek() { | ||
| 202 | + return this.list[this.head]; | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + // Two-slot (value, size) entries (default controller queues). The | ||
| 206 | + // stride is always 2 and capacities are even, so `tail + 1`/`head + 1` | ||
| 207 | + // never need to wrap. | ||
| 208 | + | ||
| 209 | + pushPair(value, size) { | ||
| 210 | + const tail = this.tail; | ||
| 211 | + const list = this.list; | ||
| 212 | + list[tail] = value; | ||
| 213 | + list[tail + 1] = size; | ||
| 214 | + this.tail = (tail + 2) & this.capacityMask; | ||
| 215 | + this.length++; | ||
| 216 | + if (this.tail === this.head) | ||
| 217 | + this.grow(); | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + // Returns the dequeued value; the size of the same entry is left in | ||
| 221 | + // `this.dequeuedSize` so that callers can update [[queueTotalSize]] | ||
| 222 | + // without a per-entry wrapper object having to exist. | ||
| 223 | + shiftPair() { | ||
| 224 | + const head = this.head; | ||
| 225 | + const list = this.list; | ||
| 226 | + const value = list[head]; | ||
| 227 | + this.dequeuedSize = list[head + 1]; | ||
| 228 | + list[head] = undefined; | ||
| 229 | + list[head + 1] = undefined; | ||
| 230 | + this.head = (head + 2) & this.capacityMask; | ||
| 231 | + if (--this.length === 0) | ||
| 232 | + this.rewind(); | ||
| 233 | + return value; | ||
| 234 | + } | ||
| 235 | + | ||
| 236 | + peekPairValue() { | ||
| 237 | + return this.list[this.head]; | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + // The ring is completely full (the post-push tail caught up with the | ||
| 241 | + // head): double the capacity, re-linearizing from the head so index | ||
| 242 | + // arithmetic stays trivial. | ||
| 243 | + grow() { | ||
| 244 | + const list = this.list; | ||
| 245 | + const capacity = list.length; | ||
| 246 | + const head = this.head; | ||
| 247 | + if (head !== 0) { | ||
| 248 | + const relinearized = new Array(capacity * 2); | ||
| 249 | + let n = 0; | ||
| 250 | + for (let i = head; i < capacity; i++) | ||
| 251 | + relinearized[n++] = list[i]; | ||
| 252 | + for (let i = 0; i < head; i++) | ||
| 253 | + relinearized[n++] = list[i]; | ||
| 254 | + this.list = relinearized; | ||
| 255 | + this.head = 0; | ||
| 256 | + } else { | ||
| 257 | + list.length = capacity * 2; | ||
| 258 | + } | ||
| 259 | + this.tail = capacity; | ||
| 260 | + this.capacityMask = (capacity * 2) - 1; | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + // The queue just became empty: restart at slot 0 so shallow queues net | ||
| 264 | + // sequential slot access, and drop the enlarged backing store after a | ||
| 265 | + // large burst has fully drained. | ||
| 266 | + rewind() { | ||
| 267 | + this.head = 0; | ||
| 268 | + this.tail = 0; | ||
| 269 | + if (this.list.length > 1024) { | ||
| 270 | + this.list.length = 8; | ||
| 271 | + this.capacityMask = 0b111; | ||
| 272 | + } | ||
| 273 | + } | ||
| 274 | + } | ||
| 275 | + | ||
| 276 | + // Controllers start out with (and are reset to) this shared immutable | ||
| 277 | + // empty queue, so constructing a stream never allocates queue storage; | ||
| 278 | + // a real Queue is materialized by the enqueue paths on first use. All | ||
| 279 | + // dequeue/peek paths are guarded by `.length` (or the equivalent | ||
| 280 | + // [[queueTotalSize]]) checks, so they can never observe the sentinel in | ||
| 281 | + // a mutating way; it never stores entries, so it gets a zero-length | ||
| 282 | + // backing list. | ||
| 283 | + const kEmptyQueue = ObjectFreeze(new Queue(0)); | ||
| 284 | + | ||
| 285 | + function materializeQueue(state) { | ||
| 286 | + const queue = state.queue; | ||
| 287 | + if (queue === kEmptyQueue) | ||
| 288 | + return state.queue = new Queue(); | ||
| 289 | + return queue; | ||
| 290 | + } | ||
| 291 | + | ||
| 155 | 292 | // The queue helpers below run once per chunk on the hot paths of every | |
| 156 | 293 | // default readable/writable stream, so they load the controller state a | |
| 157 | 294 | // single time and don't assert the existence of the queue fields (both | |
| 158 | 295 | // are unconditionally initialized during controller setup and only ever | |
| 159 | 296 | // replaced wholesale). | |
| 160 | 297 | function dequeueValue(controller) { | |
| 161 | 298 | const state = controller[kState]; | |
| 162 | - assert(state.queue.length); | ||
| 163 | - const { | ||
| 164 | - value, | ||
| 165 | - size, | ||
| 166 | - } = ArrayPrototypeShift(state.queue); | ||
| 167 | - state.queueTotalSize = MathMax(0, state.queueTotalSize - size); | ||
| 299 | + const queue = state.queue; | ||
| 300 | + assert(queue.length); | ||
| 301 | + const value = queue.shiftPair(); | ||
| 302 | + state.queueTotalSize = MathMax(0, state.queueTotalSize - queue.dequeuedSize); | ||
| 168 | 303 | return value; | |
| 169 | 304 | } | |
| 170 | 305 | ||
| 171 | 306 | function resetQueue(controller) { | |
| 172 | 307 | const state = controller[kState]; | |
| 173 | - state.queue = []; | ||
| 308 | + state.queue = kEmptyQueue; | ||
| 174 | 309 | state.queueTotalSize = 0; | |
| 175 | 310 | } | |
| 176 | 311 | ||
| 177 | 312 | function peekQueueValue(controller) { | |
| 178 | 313 | const state = controller[kState]; | |
| 179 | 314 | assert(state.queue.length); | |
| 180 | - return state.queue[0].value; | ||
| 315 | + return state.queue.peekPairValue(); | ||
| 181 | 316 | } | |
| 182 | 317 | ||
| 183 | 318 | function enqueueValueWithSize(controller, value, size) { | |
@@ -188,7 +323,7 @@ function enqueueValueWithSize(controller, value, size) { | |||
| 188 | 323 | coercedSize === Infinity) { | |
| 189 | 324 | throw new ERR_INVALID_ARG_VALUE.RangeError('size', size); | |
| 190 | 325 | } | |
| 191 | - ArrayPrototypePush(state.queue, { value, size: coercedSize }); | ||
| 326 | + materializeQueue(state).pushPair(value, coercedSize); | ||
| 192 | 327 | state.queueTotalSize += coercedSize; | |
| 193 | 328 | } | |
| 194 | 329 | ||
@@ -284,9 +419,11 @@ module.exports = { | |||
| 284 | 419 | getNonWritablePropertyDescriptor, | |
| 285 | 420 | isBrandCheck, | |
| 286 | 421 | isPromisePending, | |
| 422 | + kEmptyQueue, | ||
| 287 | 423 | kState, | |
| 288 | 424 | kType, | |
| 289 | 425 | lazyTransfer, | |
| 426 | + materializeQueue, | ||
| 290 | 427 | nonOpCancel, | |
| 291 | 428 | nonOpFlush, | |
| 292 | 429 | nonOpPull, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -67,6 +67,7 @@ const { | |||
| 67 | 67 | getNonWritablePropertyDescriptor, | |
| 68 | 68 | isBrandCheck, | |
| 69 | 69 | isPromisePending, | |
| 70 | + kEmptyQueue, | ||
| 70 | 71 | kState, | |
| 71 | 72 | kType, | |
| 72 | 73 | lazyTransfer, | |
@@ -1177,6 +1178,11 @@ function writableStreamDefaultControllerGetChunkSize(controller, chunk) { | |||
| 1177 | 1178 | return 1; | |
| 1178 | 1179 | } | |
| 1179 | 1180 | ||
| 1181 | + // The internal default size algorithm is never observable by user | ||
| 1182 | + // code, always returns 1, and cannot throw: skip the call. | ||
| 1183 | + if (sizeAlgorithm === defaultSizeAlgorithm) | ||
| 1184 | + return 1; | ||
| 1185 | + | ||
| 1180 | 1186 | try { | |
| 1181 | 1187 | return FunctionPrototypeCall( | |
| 1182 | 1188 | sizeAlgorithm, | |
@@ -1293,7 +1299,7 @@ function setupWritableStreamDefaultController( | |||
| 1293 | 1299 | abortAlgorithm, | |
| 1294 | 1300 | closeAlgorithm, | |
| 1295 | 1301 | highWaterMark, | |
| 1296 | - queue: [], | ||
| 1302 | + queue: kEmptyQueue, | ||
| 1297 | 1303 | queueTotalSize: 0, | |
| 1298 | 1304 | abortController: new AbortController(), | |
| 1299 | 1305 | sizeAlgorithm, | |
| Back | FazBrowse Home | New Git URL |
0 commit comments