| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 9445e27 commit 3043b2a
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -497,6 +497,12 @@ class ReadableStream { | |||
| 497 | 497 | current: undefined, | |
| 498 | 498 | }; | |
| 499 | 499 | let started = false; | |
| 500 | + // A single reusable read request: at most one read is ever in flight | ||
| 501 | + // (next() chains through state.current), and the request is consumed | ||
| 502 | + // before the next read starts, so only its promise record changes | ||
| 503 | + // per read. | ||
| 504 | + // eslint-disable-next-line no-use-before-define | ||
| 505 | + const readRequest = new ReadableStreamAsyncIteratorReadRequest(reader, state, undefined); | ||
| 500 | 506 | ||
| 501 | 507 | // The nextSteps function is not an async function in order | |
| 502 | 508 | // to make it more efficient. Because nextSteps explicitly | |
@@ -515,8 +521,8 @@ class ReadableStream { | |||
| 515 | 521 | } | |
| 516 | 522 | const promise = PromiseWithResolvers(); | |
| 517 | 523 | ||
| 518 | - // eslint-disable-next-line no-use-before-define | ||
| 519 | - readableStreamDefaultReaderRead(reader, new ReadableStreamAsyncIteratorReadRequest(reader, state, promise)); | ||
| 524 | + readRequest.promise = promise; | ||
| 525 | + readableStreamDefaultReaderRead(reader, readRequest); | ||
| 520 | 526 | return promise.promise; | |
| 521 | 527 | } | |
| 522 | 528 | ||
@@ -570,28 +576,38 @@ class ReadableStream { | |||
| 570 | 576 | } | |
| 571 | 577 | // No read is in flight. Mirror the buffered fast path of | |
| 572 | 578 | // ReadableStreamDefaultReader.read(): when data is already queued | |
| 573 | - // in a default controller, resolve immediately without allocating | ||
| 574 | - // a read request. The result settles synchronously, so leaving | ||
| 579 | + // in the controller, resolve immediately without allocating a | ||
| 580 | + // read request. The result settles synchronously, so leaving | ||
| 575 | 581 | // state.current undefined matches the state the slow path reaches | |
| 576 | 582 | // once its read request callbacks have settled. | |
| 577 | 583 | const stream = reader[kState].stream; | |
| 578 | - if (!state.done && stream !== undefined) { | ||
| 584 | + if (!state.done && stream !== undefined && | ||
| 585 | + stream[kState].state === 'readable') { | ||
| 579 | 586 | const controller = stream[kState].controller; | |
| 580 | - if (stream[kState].state === 'readable' && | ||
| 581 | - isReadableStreamDefaultController(controller) && | ||
| 582 | - controller[kState].queue.length > 0) { | ||
| 583 | - stream[kState].disturbed = true; | ||
| 584 | - const chunk = dequeueValue(controller); | ||
| 585 | - | ||
| 586 | - if (controller[kState].closeRequested && | ||
| 587 | - !controller[kState].queue.length) { | ||
| 588 | - readableStreamDefaultControllerClearAlgorithms(controller); | ||
| 589 | - readableStreamClose(stream); | ||
| 590 | - } else { | ||
| 591 | - readableStreamDefaultControllerCallPullIfNeeded(controller); | ||
| 587 | + if (isReadableStreamDefaultController(controller)) { | ||
| 588 | + if (controller[kState].queue.length > 0) { | ||
| 589 | + stream[kState].disturbed = true; | ||
| 590 | + const chunk = dequeueValue(controller); | ||
| 591 | + | ||
| 592 | + if (controller[kState].closeRequested && | ||
| 593 | + !controller[kState].queue.length) { | ||
| 594 | + readableStreamDefaultControllerClearAlgorithms(controller); | ||
| 595 | + readableStreamClose(stream); | ||
| 596 | + } else { | ||
| 597 | + readableStreamDefaultControllerCallPullIfNeeded(controller); | ||
| 598 | + } | ||
| 599 | + | ||
| 600 | + return PromiseResolve({ done: false, value: chunk }); | ||
| 592 | 601 | } | |
| 602 | + } else if (controller[kState].queueTotalSize > 0) { | ||
| 603 | + // Byte controller with buffered data: same shape as above via | ||
| 604 | + // the queue-filled arm of the byte controller's pull steps. | ||
| 605 | + stream[kState].disturbed = true; | ||
| 606 | + return PromiseResolve({ | ||
| 607 | + done: false, | ||
| 593 | 608 | ||
| 594 | - return PromiseResolve({ done: false, value: chunk }); | ||
| 609 | + value: readableByteStreamControllerDequeueChunk(controller), | ||
| 610 | + }); | ||
| 595 | 611 | } | |
| 596 | 612 | } | |
| 597 | 613 | state.current = nextSteps(); | |
@@ -914,24 +930,36 @@ class ReadableStreamDefaultReader { | |||
| 914 | 930 | const stream = this[kState].stream; | |
| 915 | 931 | const controller = stream[kState].controller; | |
| 916 | 932 | ||
| 917 | - // Fast path: if data is already buffered in a default controller, | ||
| 933 | + // Fast path: if data is already buffered in the controller's queue, | ||
| 918 | 934 | // return a resolved promise immediately without creating a read request. | |
| 919 | 935 | // This is spec-compliant because read() returns a Promise, and | |
| 920 | 936 | // Promise.resolve() callbacks still run in the microtask queue. | |
| 921 | - if (stream[kState].state === 'readable' && | ||
| 922 | - isReadableStreamDefaultController(controller) && | ||
| 923 | - controller[kState].queue.length > 0) { | ||
| 924 | - stream[kState].disturbed = true; | ||
| 925 | - const chunk = dequeueValue(controller); | ||
| 937 | + if (stream[kState].state === 'readable') { | ||
| 938 | + if (isReadableStreamDefaultController(controller)) { | ||
| 939 | + if (controller[kState].queue.length > 0) { | ||
| 940 | + stream[kState].disturbed = true; | ||
| 941 | + const chunk = dequeueValue(controller); | ||
| 942 | + | ||
| 943 | + if (controller[kState].closeRequested && !controller[kState].queue.length) { | ||
| 944 | + readableStreamDefaultControllerClearAlgorithms(controller); | ||
| 945 | + readableStreamClose(stream); | ||
| 946 | + } else { | ||
| 947 | + readableStreamDefaultControllerCallPullIfNeeded(controller); | ||
| 948 | + } | ||
| 926 | 949 | ||
| 927 | - if (controller[kState].closeRequested && !controller[kState].queue.length) { | ||
| 928 | - readableStreamDefaultControllerClearAlgorithms(controller); | ||
| 929 | - readableStreamClose(stream); | ||
| 930 | - } else { | ||
| 931 | - readableStreamDefaultControllerCallPullIfNeeded(controller); | ||
| 950 | + return PromiseResolve({ done: false, value: chunk }); | ||
| 951 | + } | ||
| 952 | + } else if (controller[kState].queueTotalSize > 0) { | ||
| 953 | + // Byte controller with buffered data: mirror the queue-filled arm | ||
| 954 | + // of its pull steps (which never consults pendingPullIntos) minus | ||
| 955 | + // the read request. | ||
| 956 | + stream[kState].disturbed = true; | ||
| 957 | + return PromiseResolve({ | ||
| 958 | + done: false, | ||
| 959 | + | ||
| 960 | + value: readableByteStreamControllerDequeueChunk(controller), | ||
| 961 | + }); | ||
| 932 | 962 | } | |
| 933 | - | ||
| 934 | - return PromiseResolve({ value: chunk, done: false }); | ||
| 935 | 963 | } | |
| 936 | 964 | ||
| 937 | 965 | // Slow path: create request and go through normal flow | |
@@ -3040,9 +3068,23 @@ function readableByteStreamControllerEnqueue(controller, chunk) { | |||
| 3040 | 3068 | } | |
| 3041 | 3069 | } | |
| 3042 | 3070 | ||
| 3043 | - if (readableStreamHasDefaultReader(stream)) { | ||
| 3044 | - readableByteStreamControllerProcessReadRequestsUsingQueue(controller); | ||
| 3045 | - if (!readableStreamGetNumReadRequests(stream)) { | ||
| 3071 | + // Single consolidated pass over the reader state. The spec routes this | ||
| 3072 | + // through HasDefaultReader / ProcessReadRequestsUsingQueue / | ||
| 3073 | + // GetNumReadRequests / FulfillReadRequest, which would re-run the same | ||
| 3074 | + // reader brand check and re-load the read request list four times on | ||
| 3075 | + // this per-chunk path. | ||
| 3076 | + const { reader } = stream[kState]; | ||
| 3077 | + if (reader !== undefined && | ||
| 3078 | + reader[kState] !== undefined && | ||
| 3079 | + reader[kType] === 'ReadableStreamDefaultReader') { | ||
| 3080 | + const { readRequests } = reader[kState]; | ||
| 3081 | + if (readRequests.length && controller[kState].queueTotalSize > 0) { | ||
| 3082 | + // Only possible when data was enqueued while the stream was not | ||
| 3083 | + // being read; read requests otherwise never coexist with a | ||
| 3084 | + // non-empty queue. | ||
| 3085 | + readableByteStreamControllerProcessReadRequestsUsingQueue(controller); | ||
| 3086 | + } | ||
| 3087 | + if (!readRequests.length) { | ||
| 3046 | 3088 | readableByteStreamControllerEnqueueChunkToQueue( | |
| 3047 | 3089 | controller, | |
| 3048 | 3090 | transferredBuffer, | |
@@ -3056,7 +3098,8 @@ function readableByteStreamControllerEnqueue(controller, chunk) { | |||
| 3056 | 3098 | } | |
| 3057 | 3099 | const transferredView = | |
| 3058 | 3100 | new Uint8Array(transferredBuffer, byteOffset, byteLength); | |
| 3059 | - readableStreamFulfillReadRequest(stream, transferredView, false); | ||
| 3101 | + const readRequest = ArrayPrototypeShift(readRequests); | ||
| 3102 | + readRequest[kChunk](transferredView); | ||
| 3060 | 3103 | } | |
| 3061 | 3104 | } else if (readableStreamHasBYOBReader(stream)) { | |
| 3062 | 3105 | readableByteStreamControllerEnqueueChunkToQueue( | |
@@ -3391,22 +3434,28 @@ function readableByteStreamControllerCancelSteps(controller, reason) { | |||
| 3391 | 3434 | return result; | |
| 3392 | 3435 | } | |
| 3393 | 3436 | ||
| 3394 | - function readableByteStreamControllerFillReadRequestFromQueue(controller, readRequest) { | ||
| 3395 | - const { | ||
| 3396 | - queue, | ||
| 3397 | - queueTotalSize, | ||
| 3398 | - } = controller[kState]; | ||
| 3399 | - assert(queueTotalSize > 0); | ||
| 3437 | + // Dequeues the first chunk of the byte queue as a Uint8Array view, | ||
| 3438 | + // handling queue drain (close-on-empty or pull) before the view is | ||
| 3439 | + // created. This is the [[queueTotalSize]] > 0 arm of the byte | ||
| 3440 | + // controller's pull steps; it is also called directly from the | ||
| 3441 | + // buffered fast paths in ReadableStreamDefaultReader.read() and the | ||
| 3442 | + // async iterator, which resolve with the view without allocating a | ||
| 3443 | + // read request. | ||
| 3444 | + function readableByteStreamControllerDequeueChunk(controller) { | ||
| 3445 | + assert(controller[kState].queueTotalSize > 0); | ||
| 3400 | 3446 | const { | |
| 3401 | 3447 | buffer, | |
| 3402 | 3448 | byteOffset, | |
| 3403 | 3449 | byteLength, | |
| 3404 | - } = ArrayPrototypeShift(queue); | ||
| 3450 | + } = ArrayPrototypeShift(controller[kState].queue); | ||
| 3405 | 3451 | ||
| 3406 | 3452 | controller[kState].queueTotalSize -= byteLength; | |
| 3407 | 3453 | readableByteStreamControllerHandleQueueDrain(controller); | |
| 3408 | - const view = new Uint8Array(buffer, byteOffset, byteLength); | ||
| 3409 | - readRequest[kChunk](view); | ||
| 3454 | + return new Uint8Array(buffer, byteOffset, byteLength); | ||
| 3455 | + } | ||
| 3456 | + | ||
| 3457 | + function readableByteStreamControllerFillReadRequestFromQueue(controller, readRequest) { | ||
| 3458 | + readRequest[kChunk](readableByteStreamControllerDequeueChunk(controller)); | ||
| 3410 | 3459 | } | |
| 3411 | 3460 | ||
| 3412 | 3461 | function readableByteStreamControllerProcessReadRequestsUsingQueue(controller) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,14 +7,19 @@ const { | |||
| 7 | 7 | ArrayPrototypePush, | |
| 8 | 8 | ArrayPrototypeShift, | |
| 9 | 9 | AsyncIteratorPrototype, | |
| 10 | + DataViewPrototypeGetBuffer, | ||
| 11 | + DataViewPrototypeGetByteLength, | ||
| 12 | + DataViewPrototypeGetByteOffset, | ||
| 10 | 13 | FunctionPrototypeCall, | |
| 11 | 14 | MathMax, | |
| 12 | 15 | NumberIsNaN, | |
| 13 | 16 | PromisePrototypeThen, | |
| 14 | 17 | PromiseReject, | |
| 15 | 18 | PromiseResolve, | |
| 16 | - ReflectGet, | ||
| 17 | 19 | Symbol, | |
| 20 | + TypedArrayPrototypeGetBuffer, | ||
| 21 | + TypedArrayPrototypeGetByteLength, | ||
| 22 | + TypedArrayPrototypeGetByteOffset, | ||
| 18 | 23 | Uint8Array, | |
| 19 | 24 | } = primordials; | |
| 20 | 25 | ||
@@ -41,6 +46,10 @@ const { | |||
| 41 | 46 | ||
| 42 | 47 | const assert = require('internal/assert'); | |
| 43 | 48 | ||
| 49 | + const { | ||
| 50 | + isDataView, | ||
| 51 | + } = require('internal/util/types'); | ||
| 52 | + | ||
| 44 | 53 | const { | |
| 45 | 54 | validateFunction, | |
| 46 | 55 | } = require('internal/validators'); | |
@@ -93,20 +102,29 @@ function customInspect(depth, options, name, data) { | |||
| 93 | 102 | return `${name} ${inspect(data, opts)}`; | |
| 94 | 103 | } | |
| 95 | 104 | ||
| 96 | - // These are defensive to work around the possibility that | ||
| 97 | - // the buffer, byteLength, and byteOffset properties on | ||
| 98 | - // ArrayBuffer and ArrayBufferView's may have been tampered with. | ||
| 105 | + // These use the original prototype getters so that user tampering with | ||
| 106 | + // the buffer, byteLength, and byteOffset properties on ArrayBuffer and | ||
| 107 | + // ArrayBufferView's is not observed. They run once or more per chunk on | ||
| 108 | + // every byte-stream path, so they must not go through a reflective get | ||
| 109 | + // (the previous view.constructor.prototype lookup was both slower and | ||
| 110 | + // spoofable via a user-defined .constructor). | ||
| 99 | 111 | ||
| 100 | 112 | function ArrayBufferViewGetBuffer(view) { | |
| 101 | - return ReflectGet(view.constructor.prototype, 'buffer', view); | ||
| 113 | + return isDataView(view) ? | ||
| 114 | + DataViewPrototypeGetBuffer(view) : | ||
| 115 | + TypedArrayPrototypeGetBuffer(view); | ||
| 102 | 116 | } | |
| 103 | 117 | ||
| 104 | 118 | function ArrayBufferViewGetByteLength(view) { | |
| 105 | - return ReflectGet(view.constructor.prototype, 'byteLength', view); | ||
| 119 | + return isDataView(view) ? | ||
| 120 | + DataViewPrototypeGetByteLength(view) : | ||
| 121 | + TypedArrayPrototypeGetByteLength(view); | ||
| 106 | 122 | } | |
| 107 | 123 | ||
| 108 | 124 | function ArrayBufferViewGetByteOffset(view) { | |
| 109 | - return ReflectGet(view.constructor.prototype, 'byteOffset', view); | ||
| 125 | + return isDataView(view) ? | ||
| 126 | + DataViewPrototypeGetByteOffset(view) : | ||
| 127 | + TypedArrayPrototypeGetByteOffset(view); | ||
| 110 | 128 | } | |
| 111 | 129 | ||
| 112 | 130 | function cloneAsUint8Array(view) { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments