| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 557bf99 commit a46ddad
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1649,7 +1649,7 @@ Creates a classic [`stream.Writable`][] backed by a stream/iter Writer. | |||
| 1649 | 1649 | ||
| 1650 | 1650 | Each `_write()` / `_writev()` call attempts the Writer's synchronous method | |
| 1651 | 1651 | first (`writeSync` / `writevSync`), falling back to the async method if the | |
| 1652 | - sync path returns `false` or throws. Similarly, `_final()` tries `endSync()` | ||
| 1652 | + sync path returns `false`. Similarly, `_final()` tries `endSync()` | ||
| 1653 | 1653 | before `end()`. When the sync path succeeds, the callback is deferred via | |
| 1654 | 1654 | `queueMicrotask` to preserve the async resolution contract. | |
| 1655 | 1655 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -57,7 +57,6 @@ const { | |||
| 57 | 57 | const { | |
| 58 | 58 | toAsyncStreamable: kToAsyncStreamable, | |
| 59 | 59 | kValidatedSource, | |
| 60 | - kSyncWriteAccepted, | ||
| 61 | 60 | drainableProtocol, | |
| 62 | 61 | } = require('internal/streams/iter/types'); | |
| 63 | 62 | ||
@@ -765,41 +764,11 @@ function toWritable(writer) { | |||
| 765 | 764 | const hasEndSync = hasEnd && | |
| 766 | 765 | typeof writer.endSync === 'function'; | |
| 767 | 766 | const hasFail = typeof writer.fail === 'function'; | |
| 768 | - const hasSyncWriteAccepted = | ||
| 769 | - typeof writer[kSyncWriteAccepted] === 'function'; | ||
| 770 | - | ||
| 771 | - function syncWriteAccepted() { | ||
| 772 | - return hasSyncWriteAccepted && writer[kSyncWriteAccepted](); | ||
| 773 | - } | ||
| 774 | - | ||
| 775 | - function finishAfterSyncBackpressure(cb) { | ||
| 776 | - let ondrain; | ||
| 777 | - try { | ||
| 778 | - if (typeof writer[drainableProtocol] === 'function') { | ||
| 779 | - ondrain = writer[drainableProtocol](); | ||
| 780 | - } | ||
| 781 | - } catch (err) { | ||
| 782 | - cb(err); | ||
| 783 | - return; | ||
| 784 | - } | ||
| 785 | - if (ondrain !== null && ondrain !== undefined) { | ||
| 786 | - PromisePrototypeThen(ondrain, (drained) => { | ||
| 787 | - if (drained === false) { | ||
| 788 | - cb(new ERR_INVALID_STATE.TypeError('Stream closed by consumer')); | ||
| 789 | - return; | ||
| 790 | - } | ||
| 791 | - cb(); | ||
| 792 | - }, cb); | ||
| 793 | - return; | ||
| 794 | - } | ||
| 795 | - queueMicrotask(cb); | ||
| 796 | - } | ||
| 797 | - | ||
| 798 | 767 | // Try-sync-first pattern: attempt the synchronous method and fall back to the | |
| 799 | - // async method if it returns false without accepting the data, or if it | ||
| 800 | - // throws. When the sync path succeeds, the callback is deferred via | ||
| 801 | - // queueMicrotask to preserve the async resolution contract that Writable | ||
| 802 | - // internals expect from _write/_writev/_final callbacks. | ||
| 768 | + // async method if it returns false (data not accepted synchronously). | ||
| 769 | + // When the sync path succeeds, the callback is deferred via queueMicrotask | ||
| 770 | + // to preserve the async resolution contract that Writable internals expect | ||
| 771 | + // from _write/_writev/_final callbacks. | ||
| 803 | 772 | ||
| 804 | 773 | function _write(chunk, encoding, cb) { | |
| 805 | 774 | const bytes = typeof chunk === 'string' ? | |
@@ -810,13 +779,10 @@ function toWritable(writer) { | |||
| 810 | 779 | queueMicrotask(cb); | |
| 811 | 780 | return; | |
| 812 | 781 | } | |
| 813 | - if (syncWriteAccepted()) { | ||
| 814 | - // The chunk was accepted; false only signaled backpressure. | ||
| 815 | - finishAfterSyncBackpressure(cb); | ||
| 816 | - return; | ||
| 817 | - } | ||
| 818 | - } catch { | ||
| 819 | - // Sync path threw -- fall through to async. | ||
| 782 | + // WriteSync returned false: not accepted, fall through to async. | ||
| 783 | + } catch (err) { | ||
| 784 | + cb(err); | ||
| 785 | + return; | ||
| 820 | 786 | } | |
| 821 | 787 | } | |
| 822 | 788 | try { | |
@@ -839,13 +805,10 @@ function toWritable(writer) { | |||
| 839 | 805 | queueMicrotask(cb); | |
| 840 | 806 | return; | |
| 841 | 807 | } | |
| 842 | - if (syncWriteAccepted()) { | ||
| 843 | - // The chunks were accepted; false only signaled backpressure. | ||
| 844 | - finishAfterSyncBackpressure(cb); | ||
| 845 | - return; | ||
| 846 | - } | ||
| 847 | - } catch { | ||
| 848 | - // Sync path threw -- fall through to async. | ||
| 808 | + // WritevSync returned false: not accepted, fall through to async. | ||
| 809 | + } catch (err) { | ||
| 810 | + cb(err); | ||
| 811 | + return; | ||
| 849 | 812 | } | |
| 850 | 813 | } | |
| 851 | 814 | try { | |
@@ -867,8 +830,10 @@ function toWritable(writer) { | |||
| 867 | 830 | queueMicrotask(cb); | |
| 868 | 831 | return; | |
| 869 | 832 | } | |
| 870 | - } catch { | ||
| 871 | - // Sync path threw -- fall through to async. | ||
| 833 | + // Result < 0: can't end synchronously, fall through to async. | ||
| 834 | + } catch (err) { | ||
| 835 | + cb(err); | ||
| 836 | + return; | ||
| 872 | 837 | } | |
| 873 | 838 | } | |
| 874 | 839 | try { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -480,6 +480,7 @@ function merge(...args) { | |||
| 480 | 480 | ); | |
| 481 | 481 | } | |
| 482 | 482 | ||
| 483 | + let primaryError; | ||
| 483 | 484 | try { | |
| 484 | 485 | while (activeCount > 0 || ready.length > 0) { | |
| 485 | 486 | signal?.throwIfAborted(); | |
@@ -500,22 +501,46 @@ function merge(...args) { | |||
| 500 | 501 | }); | |
| 501 | 502 | } | |
| 502 | 503 | } | |
| 504 | + } catch (err) { | ||
| 505 | + primaryError = err; | ||
| 503 | 506 | } finally { | |
| 504 | - // Clean up: return all iterators | ||
| 505 | - await SafePromiseAllReturnVoid(iterators, async (iterator) => { | ||
| 506 | - if (iterator.return) { | ||
| 507 | - try { | ||
| 508 | - await iterator.return(); | ||
| 509 | - } catch { | ||
| 510 | - // Ignore return errors | ||
| 511 | - } | ||
| 512 | - } | ||
| 513 | - }); | ||
| 507 | + // Clean up: return all iterators. Cleanup errors are not | ||
| 508 | + // swallowed - a broken iterator.return() (e.g., failing to | ||
| 509 | + // release a resource) should be visible to the caller. | ||
| 510 | + await cleanupIterators(iterators, primaryError); | ||
| 514 | 511 | } | |
| 515 | 512 | }, | |
| 516 | 513 | }; | |
| 517 | 514 | } | |
| 518 | 515 | ||
| 516 | + async function cleanupIterators(iterators, primaryError) { | ||
| 517 | + let cleanupError; | ||
| 518 | + await SafePromiseAllReturnVoid(iterators, async (iterator) => { | ||
| 519 | + if (iterator.return) { | ||
| 520 | + try { | ||
| 521 | + await iterator.return(); | ||
| 522 | + } catch (err) { | ||
| 523 | + // Keep the first cleanup error encountered. | ||
| 524 | + cleanupError ??= err; | ||
| 525 | + } | ||
| 526 | + } | ||
| 527 | + }); | ||
| 528 | + if (cleanupError !== undefined) { | ||
| 529 | + if (primaryError !== undefined) { | ||
| 530 | + // Both a primary error and a cleanup error occurred. | ||
| 531 | + // Wrap in SuppressedError so neither is lost: | ||
| 532 | + // .error = primaryError, .suppressed = cleanupError. | ||
| 533 | + // eslint-disable-next-line no-restricted-syntax | ||
| 534 | + throw new SuppressedError(primaryError, cleanupError); | ||
| 535 | + } | ||
| 536 | + // No primary error - the cleanup error is the only error. | ||
| 537 | + throw cleanupError; | ||
| 538 | + } | ||
| 539 | + if (primaryError !== undefined) { | ||
| 540 | + throw primaryError; | ||
| 541 | + } | ||
| 542 | + } | ||
| 543 | + | ||
| 519 | 544 | module.exports = { | |
| 520 | 545 | array, | |
| 521 | 546 | arrayBuffer, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -63,8 +63,6 @@ const { | |||
| 63 | 63 | } = require('internal/streams/iter/utils'); | |
| 64 | 64 | ||
| 65 | 65 | const { | |
| 66 | - drainableProtocol, | ||
| 67 | - kSyncWriteAcceptedOnFalse, | ||
| 68 | 66 | kValidatedSource, | |
| 69 | 67 | kValidatedTransform, | |
| 70 | 68 | toAsyncStreamable, | |
@@ -863,18 +861,6 @@ async function* createAsyncPipeline(source, transforms, signal) { | |||
| 863 | 861 | } | |
| 864 | 862 | } | |
| 865 | 863 | ||
| 866 | - /** | ||
| 867 | - * Check if a false sync write result means accepted backpressure. | ||
| 868 | - * @param {object} writer - The writer whose sync method returned. | ||
| 869 | - * @param {*} result - The return value from writeSync() or writevSync(). | ||
| 870 | - * @returns {boolean} | ||
| 871 | - */ | ||
| 872 | - function isAcceptedSyncWriteBackpressure(writer, result) { | ||
| 873 | - return result === false && | ||
| 874 | - writer[kSyncWriteAcceptedOnFalse] === true && | ||
| 875 | - writer.desiredSize === 0; | ||
| 876 | - } | ||
| 877 | - | ||
| 878 | 864 | // ============================================================================= | |
| 879 | 865 | // Public API: pull() and pullSync() | |
| 880 | 866 | // ============================================================================= | |
@@ -963,9 +949,7 @@ function pipeToSync(source, ...args) { | |||
| 963 | 949 | break; | |
| 964 | 950 | } | |
| 965 | 951 | if (hasWritevSync && batch.length > 1) { | |
| 966 | - const result = writer.writevSync(batch); | ||
| 967 | - if (result === false && | ||
| 968 | - !isAcceptedSyncWriteBackpressure(writer, result)) { | ||
| 952 | + if (writer.writevSync(batch) === false) { | ||
| 969 | 953 | break; | |
| 970 | 954 | } | |
| 971 | 955 | for (let i = 0; i < batch.length; i++) { | |
@@ -974,9 +958,7 @@ function pipeToSync(source, ...args) { | |||
| 974 | 958 | } else { | |
| 975 | 959 | for (let i = 0; i < batch.length; i++) { | |
| 976 | 960 | const chunk = batch[i]; | |
| 977 | - const result = writer.writeSync(chunk); | ||
| 978 | - if (result === false && | ||
| 979 | - !isAcceptedSyncWriteBackpressure(writer, result)) { | ||
| 961 | + if (writer.writeSync(chunk) === false) { | ||
| 980 | 962 | canContinue = false; | |
| 981 | 963 | break; | |
| 982 | 964 | } | |
@@ -1027,28 +1009,13 @@ async function pipeTo(source, ...args) { | |||
| 1027 | 1009 | const hasWritevSync = typeof writer.writevSync === 'function'; | |
| 1028 | 1010 | const hasEndSync = typeof writer.endSync === 'function'; | |
| 1029 | 1011 | ||
| 1030 | - function waitForSyncBackpressure() { | ||
| 1031 | - const ondrain = writer[drainableProtocol]; | ||
| 1032 | - return ondrain?.call(writer); | ||
| 1033 | - } | ||
| 1034 | - | ||
| 1035 | - async function writeBatchAfterAcceptedBackpressure(batch, startIndex) { | ||
| 1036 | - await waitForSyncBackpressure(); | ||
| 1037 | - await writeBatchAsyncFallback(batch, startIndex); | ||
| 1038 | - } | ||
| 1039 | - | ||
| 1040 | 1012 | // Async fallback for writeBatch when sync write fails partway through. | |
| 1041 | 1013 | // Continues writing from batch[startIndex] using async write(). | |
| 1042 | 1014 | async function writeBatchAsyncFallback(batch, startIndex) { | |
| 1043 | 1015 | for (let i = startIndex; i < batch.length; i++) { | |
| 1044 | 1016 | const chunk = batch[i]; | |
| 1045 | - const result = hasWriteSync && writer.writeSync(chunk); | ||
| 1046 | - if (result) { | ||
| 1017 | + if (hasWriteSync && writer.writeSync(chunk)) { | ||
| 1047 | 1018 | // Sync retry succeeded | |
| 1048 | - } else if (isAcceptedSyncWriteBackpressure(writer, result)) { | ||
| 1049 | - totalBytes += TypedArrayPrototypeGetByteLength(chunk); | ||
| 1050 | - await waitForSyncBackpressure(); | ||
| 1051 | - continue; | ||
| 1052 | 1019 | } else { | |
| 1053 | 1020 | const result = writer.write( | |
| 1054 | 1021 | chunk, signal ? { __proto__: null, signal } : undefined); | |
@@ -1065,14 +1032,7 @@ async function pipeTo(source, ...args) { | |||
| 1065 | 1032 | // is required. Callers must check: const p = writeBatch(b); if (p) await p; | |
| 1066 | 1033 | function writeBatch(batch) { | |
| 1067 | 1034 | if (hasWritev && batch.length > 1) { | |
| 1068 | - const result = hasWritevSync && writer.writevSync(batch); | ||
| 1069 | - if (!result) { | ||
| 1070 | - if (isAcceptedSyncWriteBackpressure(writer, result)) { | ||
| 1071 | - for (let i = 0; i < batch.length; i++) { | ||
| 1072 | - totalBytes += TypedArrayPrototypeGetByteLength(batch[i]); | ||
| 1073 | - } | ||
| 1074 | - return waitForSyncBackpressure(); | ||
| 1075 | - } | ||
| 1035 | + if (!hasWritevSync || !writer.writevSync(batch)) { | ||
| 1076 | 1036 | const opts = signal ? { __proto__: null, signal } : undefined; | |
| 1077 | 1037 | const writevResult = writer.writev(batch, opts); | |
| 1078 | 1038 | if (writevResult === undefined) { | |
@@ -1094,14 +1054,8 @@ async function pipeTo(source, ...args) { | |||
| 1094 | 1054 | } | |
| 1095 | 1055 | for (let i = 0; i < batch.length; i++) { | |
| 1096 | 1056 | const chunk = batch[i]; | |
| 1097 | - const result = hasWriteSync && writer.writeSync(chunk); | ||
| 1098 | - if (!result) { | ||
| 1099 | - if (isAcceptedSyncWriteBackpressure(writer, result)) { | ||
| 1100 | - totalBytes += TypedArrayPrototypeGetByteLength(chunk); | ||
| 1101 | - return writeBatchAfterAcceptedBackpressure(batch, i + 1); | ||
| 1102 | - } | ||
| 1057 | + if (!hasWriteSync || !writer.writeSync(chunk)) { | ||
| 1103 | 1058 | // Sync path failed at index i - fall back to async for the rest. | |
| 1104 | - // Count bytes for chunks already written synchronously (0..i-1). | ||
| 1105 | 1059 | return writeBatchAsyncFallback(batch, i); | |
| 1106 | 1060 | } | |
| 1107 | 1061 | totalBytes += TypedArrayPrototypeGetByteLength(chunk); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,8 +32,6 @@ const { | |||
| 32 | 32 | ||
| 33 | 33 | const { | |
| 34 | 34 | drainableProtocol, | |
| 35 | - kSyncWriteAccepted, | ||
| 36 | - kSyncWriteAcceptedOnFalse, | ||
| 37 | 35 | } = require('internal/streams/iter/types'); | |
| 38 | 36 | ||
| 39 | 37 | const { | |
@@ -364,19 +362,6 @@ class PushQueue { | |||
| 364 | 362 | this.#pendingEnd = pending; | |
| 365 | 363 | } | |
| 366 | 364 | ||
| 367 | - /** | ||
| 368 | - * Force-enqueue chunks into the slots buffer, bypassing capacity checks. | ||
| 369 | - * Used by PushWriter.writeSync() for 'block' policy where the data is | ||
| 370 | - * accepted but false is returned as a backpressure signal. | ||
| 371 | - */ | ||
| 372 | - forceEnqueue(chunks) { | ||
| 373 | - this.#slots.push(chunks); | ||
| 374 | - for (let i = 0; i < chunks.length; i++) { | ||
| 375 | - this.#bytesWritten += TypedArrayPrototypeGetByteLength(chunks[i]); | ||
| 376 | - } | ||
| 377 | - this.#resolvePendingReads(); | ||
| 378 | - } | ||
| 379 | - | ||
| 380 | 365 | /** | |
| 381 | 366 | * Wait for backpressure to clear (desiredSize > 0). | |
| 382 | 367 | * @returns {Promise<void>} | |
@@ -558,16 +543,11 @@ class PushQueue { | |||
| 558 | 543 | ||
| 559 | 544 | class PushWriter { | |
| 560 | 545 | #queue; | |
| 561 | - #syncWriteAccepted = false; | ||
| 562 | 546 | ||
| 563 | 547 | constructor(queue) { | |
| 564 | 548 | this.#queue = queue; | |
| 565 | 549 | } | |
| 566 | 550 | ||
| 567 | - [kSyncWriteAccepted]() { | ||
| 568 | - return this.#syncWriteAccepted; | ||
| 569 | - } | ||
| 570 | - | ||
| 571 | 551 | [drainableProtocol]() { | |
| 572 | 552 | const desired = this.desiredSize; | |
| 573 | 553 | if (desired === null) return null; | |
@@ -579,10 +559,6 @@ class PushWriter { | |||
| 579 | 559 | return this.#queue.desiredSize; | |
| 580 | 560 | } | |
| 581 | 561 | ||
| 582 | - get [kSyncWriteAcceptedOnFalse]() { | ||
| 583 | - return this.#queue.backpressurePolicy === 'block'; | ||
| 584 | - } | ||
| 585 | - | ||
| 586 | 562 | write(chunk, options) { | |
| 587 | 563 | if (!options?.signal && this.#queue.canWriteSync()) { | |
| 588 | 564 | const bytes = toUint8Array(chunk); | |
@@ -607,36 +583,16 @@ class PushWriter { | |||
| 607 | 583 | } | |
| 608 | 584 | ||
| 609 | 585 | writeSync(chunk) { | |
| 610 | - this.#syncWriteAccepted = false; | ||
| 611 | 586 | const bytes = toUint8Array(chunk); | |
| 612 | - const result = this.#queue.writeSync([bytes]); | ||
| 613 | - if (!result && this.#queue.backpressurePolicy === 'block' && | ||
| 614 | - this.#queue.desiredSize === 0) { | ||
| 615 | - // Block policy: force-enqueue and return false as backpressure signal. | ||
| 616 | - // Data IS accepted; false tells caller to slow down. | ||
| 617 | - this.#queue.forceEnqueue([bytes]); | ||
| 618 | - this.#syncWriteAccepted = true; | ||
| 619 | - return false; | ||
| 620 | - } | ||
| 621 | - this.#syncWriteAccepted = result; | ||
| 622 | - return result; | ||
| 587 | + return this.#queue.writeSync([bytes]); | ||
| 623 | 588 | } | |
| 624 | 589 | ||
| 625 | 590 | writevSync(chunks) { | |
| 626 | - this.#syncWriteAccepted = false; | ||
| 627 | 591 | if (!ArrayIsArray(chunks)) { | |
| 628 | 592 | throw new ERR_INVALID_ARG_TYPE('chunks', 'Array', chunks); | |
| 629 | 593 | } | |
| 630 | 594 | const bytes = convertChunks(chunks); | |
| 631 | - const result = this.#queue.writeSync(bytes); | ||
| 632 | - if (!result && this.#queue.backpressurePolicy === 'block' && | ||
| 633 | - this.#queue.desiredSize === 0) { | ||
| 634 | - this.#queue.forceEnqueue(bytes); | ||
| 635 | - this.#syncWriteAccepted = true; | ||
| 636 | - return false; | ||
| 637 | - } | ||
| 638 | - this.#syncWriteAccepted = result; | ||
| 639 | - return result; | ||
| 595 | + return this.#queue.writeSync(bytes); | ||
| 640 | 596 | } | |
| 641 | 597 | ||
| 642 | 598 | end(options) { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments