| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 88a3392 commit 2e86855
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -57,6 +57,7 @@ const { | |||
| 57 | 57 | const { | |
| 58 | 58 | toAsyncStreamable: kToAsyncStreamable, | |
| 59 | 59 | kValidatedSource, | |
| 60 | + kSyncWriteAccepted, | ||
| 60 | 61 | drainableProtocol, | |
| 61 | 62 | } = require('internal/streams/iter/types'); | |
| 62 | 63 | ||
@@ -764,13 +765,41 @@ function toWritable(writer) { | |||
| 764 | 765 | const hasEndSync = hasEnd && | |
| 765 | 766 | typeof writer.endSync === 'function'; | |
| 766 | 767 | const hasFail = typeof writer.fail === 'function'; | |
| 768 | + const hasSyncWriteAccepted = | ||
| 769 | + typeof writer[kSyncWriteAccepted] === 'function'; | ||
| 767 | 770 | ||
| 768 | - // Try-sync-first pattern: attempt the synchronous method and | ||
| 769 | - // fall back to the async method if it returns false (indicating | ||
| 770 | - // the sync path was not accepted) or throws. When the sync path | ||
| 771 | - // succeeds, the callback is deferred via queueMicrotask to | ||
| 772 | - // preserve the async resolution contract that Writable internals | ||
| 773 | - // expect from _write/_writev/_final callbacks. | ||
| 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 | + // 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. | ||
| 774 | 803 | ||
| 775 | 804 | function _write(chunk, encoding, cb) { | |
| 776 | 805 | const bytes = typeof chunk === 'string' ? | |
@@ -781,6 +810,11 @@ function toWritable(writer) { | |||
| 781 | 810 | queueMicrotask(cb); | |
| 782 | 811 | return; | |
| 783 | 812 | } | |
| 813 | + if (syncWriteAccepted()) { | ||
| 814 | + // The chunk was accepted; false only signaled backpressure. | ||
| 815 | + finishAfterSyncBackpressure(cb); | ||
| 816 | + return; | ||
| 817 | + } | ||
| 784 | 818 | } catch { | |
| 785 | 819 | // Sync path threw -- fall through to async. | |
| 786 | 820 | } | |
@@ -805,6 +839,11 @@ function toWritable(writer) { | |||
| 805 | 839 | queueMicrotask(cb); | |
| 806 | 840 | return; | |
| 807 | 841 | } | |
| 842 | + if (syncWriteAccepted()) { | ||
| 843 | + // The chunks were accepted; false only signaled backpressure. | ||
| 844 | + finishAfterSyncBackpressure(cb); | ||
| 845 | + return; | ||
| 846 | + } | ||
| 808 | 847 | } catch { | |
| 809 | 848 | // Sync path threw -- fall through to async. | |
| 810 | 849 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,6 +32,7 @@ const { | |||
| 32 | 32 | ||
| 33 | 33 | const { | |
| 34 | 34 | drainableProtocol, | |
| 35 | + kSyncWriteAccepted, | ||
| 35 | 36 | kSyncWriteAcceptedOnFalse, | |
| 36 | 37 | } = require('internal/streams/iter/types'); | |
| 37 | 38 | ||
@@ -545,11 +546,16 @@ class PushQueue { | |||
| 545 | 546 | ||
| 546 | 547 | class PushWriter { | |
| 547 | 548 | #queue; | |
| 549 | + #syncWriteAccepted = false; | ||
| 548 | 550 | ||
| 549 | 551 | constructor(queue) { | |
| 550 | 552 | this.#queue = queue; | |
| 551 | 553 | } | |
| 552 | 554 | ||
| 555 | + [kSyncWriteAccepted]() { | ||
| 556 | + return this.#syncWriteAccepted; | ||
| 557 | + } | ||
| 558 | + | ||
| 553 | 559 | [drainableProtocol]() { | |
| 554 | 560 | const desired = this.desiredSize; | |
| 555 | 561 | if (desired === null) return null; | |
@@ -589,19 +595,23 @@ class PushWriter { | |||
| 589 | 595 | } | |
| 590 | 596 | ||
| 591 | 597 | writeSync(chunk) { | |
| 598 | + this.#syncWriteAccepted = false; | ||
| 592 | 599 | const bytes = toUint8Array(chunk); | |
| 593 | 600 | const result = this.#queue.writeSync([bytes]); | |
| 594 | 601 | if (!result && this.#queue.backpressurePolicy === 'block' && | |
| 595 | 602 | this.#queue.desiredSize === 0) { | |
| 596 | 603 | // Block policy: force-enqueue and return false as backpressure signal. | |
| 597 | 604 | // Data IS accepted; false tells caller to slow down. | |
| 598 | 605 | this.#queue.forceEnqueue([bytes]); | |
| 606 | + this.#syncWriteAccepted = true; | ||
| 599 | 607 | return false; | |
| 600 | 608 | } | |
| 609 | + this.#syncWriteAccepted = result; | ||
| 601 | 610 | return result; | |
| 602 | 611 | } | |
| 603 | 612 | ||
| 604 | 613 | writevSync(chunks) { | |
| 614 | + this.#syncWriteAccepted = false; | ||
| 605 | 615 | if (!ArrayIsArray(chunks)) { | |
| 606 | 616 | throw new ERR_INVALID_ARG_TYPE('chunks', 'Array', chunks); | |
| 607 | 617 | } | |
@@ -610,8 +620,10 @@ class PushWriter { | |||
| 610 | 620 | if (!result && this.#queue.backpressurePolicy === 'block' && | |
| 611 | 621 | this.#queue.desiredSize === 0) { | |
| 612 | 622 | this.#queue.forceEnqueue(bytes); | |
| 623 | + this.#syncWriteAccepted = true; | ||
| 613 | 624 | return false; | |
| 614 | 625 | } | |
| 626 | + this.#syncWriteAccepted = result; | ||
| 615 | 627 | return result; | |
| 616 | 628 | } | |
| 617 | 629 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -64,11 +64,24 @@ const kValidatedTransform = Symbol('kValidatedTransform'); | |||
| 64 | 64 | */ | |
| 65 | 65 | const kValidatedSource = Symbol('kValidatedSource'); | |
| 66 | 66 | ||
| 67 | + /** | ||
| 68 | + * Internal sentinel for writers whose sync write methods can return false | ||
| 69 | + * after accepting data as a backpressure signal. | ||
| 70 | + */ | ||
| 71 | + const kSyncWriteAccepted = Symbol('kSyncWriteAccepted'); | ||
| 72 | + | ||
| 73 | + /** | ||
| 74 | + * Internal sentinel for writers whose sync write methods may return false | ||
| 75 | + * after accepting data when backpressure is applied. Such writers must expose | ||
| 76 | + * desiredSize so callers can distinguish accepted backpressure from a sync | ||
| 77 | + * write that was not performed. | ||
| 78 | + */ | ||
| 67 | 79 | const kSyncWriteAcceptedOnFalse = Symbol('kSyncWriteAcceptedOnFalse'); | |
| 68 | 80 | ||
| 69 | 81 | module.exports = { | |
| 70 | 82 | broadcastProtocol, | |
| 71 | 83 | drainableProtocol, | |
| 84 | + kSyncWriteAccepted, | ||
| 72 | 85 | kSyncWriteAcceptedOnFalse, | |
| 73 | 86 | kValidatedSource, | |
| 74 | 87 | kValidatedTransform, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -335,6 +335,53 @@ async function testRoundTrip() { | |||
| 335 | 335 | assert.strictEqual(result, data); | |
| 336 | 336 | } | |
| 337 | 337 | ||
| 338 | + // ============================================================================= | ||
| 339 | + // PushWriter writeSync false accepted as backpressure is not retried | ||
| 340 | + // ============================================================================= | ||
| 341 | + | ||
| 342 | + async function testPushWriterBlockBackpressureNoDuplicate() { | ||
| 343 | + const { writer, readable } = push({ highWaterMark: 1, backpressure: 'block' }); | ||
| 344 | + const writable = toWritable(writer); | ||
| 345 | + | ||
| 346 | + await new Promise((resolve, reject) => { | ||
| 347 | + writable.write('a', (err) => { | ||
| 348 | + if (err) reject(err); | ||
| 349 | + else resolve(); | ||
| 350 | + }); | ||
| 351 | + }); | ||
| 352 | + | ||
| 353 | + writable.write('b'); | ||
| 354 | + writable.end(); | ||
| 355 | + | ||
| 356 | + const result = await text(readable); | ||
| 357 | + assert.strictEqual(result, 'ab'); | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + // ============================================================================= | ||
| 361 | + // PushWriter writevSync false accepted as backpressure is not retried | ||
| 362 | + // ============================================================================= | ||
| 363 | + | ||
| 364 | + async function testPushWriterBlockBackpressureWritevNoDuplicate() { | ||
| 365 | + const { writer, readable } = push({ highWaterMark: 1, backpressure: 'block' }); | ||
| 366 | + const writable = toWritable(writer); | ||
| 367 | + | ||
| 368 | + await new Promise((resolve, reject) => { | ||
| 369 | + writable.write('a', (err) => { | ||
| 370 | + if (err) reject(err); | ||
| 371 | + else resolve(); | ||
| 372 | + }); | ||
| 373 | + }); | ||
| 374 | + | ||
| 375 | + writable.cork(); | ||
| 376 | + writable.write('b'); | ||
| 377 | + writable.write('c'); | ||
| 378 | + writable.uncork(); | ||
| 379 | + writable.end(); | ||
| 380 | + | ||
| 381 | + const result = await text(readable); | ||
| 382 | + assert.strictEqual(result, 'abc'); | ||
| 383 | + } | ||
| 384 | + | ||
| 338 | 385 | // ============================================================================= | |
| 339 | 386 | // Multiple sequential writes | |
| 340 | 387 | // ============================================================================= | |
@@ -590,6 +637,8 @@ Promise.all([ | |||
| 590 | 637 | testWriteThrowsSyncPropagation(), | |
| 591 | 638 | testEndThrowsSyncPropagation(), | |
| 592 | 639 | testRoundTrip(), | |
| 640 | + testPushWriterBlockBackpressureNoDuplicate(), | ||
| 641 | + testPushWriterBlockBackpressureWritevNoDuplicate(), | ||
| 593 | 642 | testSequentialWrites(), | |
| 594 | 643 | testSyncCallbackDeferred(), | |
| 595 | 644 | testMinimalWriter(), | |
| Back | FazBrowse Home | New Git URL |
0 commit comments