| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent dc30379 commit 486cff4
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -630,9 +630,10 @@ class BroadcastWriter { | |||
| 630 | 630 | return false; | |
| 631 | 631 | } | |
| 632 | 632 | ||
| 633 | - // end() is synchronous internally - signal accepted for interface compliance. | ||
| 634 | 633 | end(options) { | |
| 635 | - getWriterSignal(options); | ||
| 634 | + const signal = getWriterSignal(options); | ||
| 635 | + if (signal?.aborted) return PromiseReject(signal.reason); | ||
| 636 | + | ||
| 636 | 637 | if (this.#isClosed()) return this.#closed; | |
| 637 | 638 | this.#closed = PromiseResolve(this.#totalBytes); | |
| 638 | 639 | this.#broadcast[kEnd](); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ | |||
| 8 | 8 | const { | |
| 9 | 9 | ArrayIsArray, | |
| 10 | 10 | ArrayPrototypePush, | |
| 11 | + PromisePrototypeThen, | ||
| 11 | 12 | PromiseReject, | |
| 12 | 13 | PromiseResolve, | |
| 13 | 14 | PromiseWithResolvers, | |
@@ -55,6 +56,35 @@ const { | |||
| 55 | 56 | ||
| 56 | 57 | const kNoFailReason = Symbol('kNoFailReason'); | |
| 57 | 58 | ||
| 59 | + function raceEndWithSignal(promise, signal) { | ||
| 60 | + if (!signal) return promise; | ||
| 61 | + if (signal.aborted) return PromiseReject(signal.reason); | ||
| 62 | + | ||
| 63 | + const { | ||
| 64 | + promise: signaledPromise, | ||
| 65 | + resolve, | ||
| 66 | + reject, | ||
| 67 | + } = PromiseWithResolvers(); | ||
| 68 | + const onAbort = () => reject(signal.reason); | ||
| 69 | + | ||
| 70 | + signal.addEventListener('abort', onAbort, { | ||
| 71 | + __proto__: null, | ||
| 72 | + once: true, | ||
| 73 | + }); | ||
| 74 | + PromisePrototypeThen( | ||
| 75 | + promise, | ||
| 76 | + (value) => { | ||
| 77 | + signal.removeEventListener('abort', onAbort); | ||
| 78 | + resolve(value); | ||
| 79 | + }, | ||
| 80 | + (reason) => { | ||
| 81 | + signal.removeEventListener('abort', onAbort); | ||
| 82 | + reject(reason); | ||
| 83 | + }, | ||
| 84 | + ); | ||
| 85 | + return signaledPromise; | ||
| 86 | + } | ||
| 87 | + | ||
| 58 | 88 | // ============================================================================= | |
| 59 | 89 | // PushQueue - Internal Queue with Chunk-Based Backpressure | |
| 60 | 90 | // ============================================================================= | |
@@ -628,7 +658,9 @@ class PushWriter { | |||
| 628 | 658 | } | |
| 629 | 659 | ||
| 630 | 660 | end(options) { | |
| 631 | - getWriterSignal(options); | ||
| 661 | + const signal = getWriterSignal(options); | ||
| 662 | + if (signal?.aborted) return PromiseReject(signal.reason); | ||
| 663 | + | ||
| 632 | 664 | const result = this.#queue.end(); | |
| 633 | 665 | if (result === -2) { | |
| 634 | 666 | // Errored: reject with stored error | |
@@ -639,11 +671,11 @@ class PushWriter { | |||
| 639 | 671 | // when consumer drains past the end sentinel | |
| 640 | 672 | const pendingEndPromise = this.#queue.pendingEndPromise; | |
| 641 | 673 | if (pendingEndPromise !== null) { | |
| 642 | - return pendingEndPromise; | ||
| 674 | + return raceEndWithSignal(pendingEndPromise, signal); | ||
| 643 | 675 | } | |
| 644 | 676 | const { promise, resolve, reject } = PromiseWithResolvers(); | |
| 645 | 677 | this.#queue.setPendingEnd({ __proto__: null, promise, resolve, reject }); | |
| 646 | - return promise; | ||
| 678 | + return raceEndWithSignal(promise, signal); | ||
| 647 | 679 | } | |
| 648 | 680 | // >= 0: byte count (immediate close or idempotent) | |
| 649 | 681 | return PromiseResolve(result); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -111,6 +111,22 @@ async function testWriterEnd() { | |||
| 111 | 111 | assert.strictEqual(data, 'data'); | |
| 112 | 112 | } | |
| 113 | 113 | ||
| 114 | + async function testWriterEndWithPreAbortedSignal() { | ||
| 115 | + const { writer, broadcast: bc } = broadcast(); | ||
| 116 | + const consumer = bc.push(); | ||
| 117 | + const reason = new Error('end aborted'); | ||
| 118 | + | ||
| 119 | + await assert.rejects( | ||
| 120 | + writer.end({ signal: AbortSignal.abort(reason) }), | ||
| 121 | + (error) => error === reason, | ||
| 122 | + ); | ||
| 123 | + | ||
| 124 | + // A rejected end must leave the writer open. | ||
| 125 | + await writer.write('data'); | ||
| 126 | + assert.strictEqual(await writer.end(), 4); | ||
| 127 | + assert.strictEqual(await text(consumer), 'data'); | ||
| 128 | + } | ||
| 129 | + | ||
| 114 | 130 | async function testWriterFail() { | |
| 115 | 131 | const { writer, broadcast: bc } = broadcast(); | |
| 116 | 132 | const consumer = bc.push(); | |
@@ -308,6 +324,7 @@ Promise.all([ | |||
| 308 | 324 | testWriteSync(), | |
| 309 | 325 | testWritevSync(), | |
| 310 | 326 | testWriterEnd(), | |
| 327 | + testWriterEndWithPreAbortedSignal(), | ||
| 311 | 328 | testWriterFail(), | |
| 312 | 329 | testCancelWithoutReason(), | |
| 313 | 330 | testCancelWithReason(), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -127,6 +127,22 @@ async function testAbortSignal() { | |||
| 127 | 127 | ); | |
| 128 | 128 | } | |
| 129 | 129 | ||
| 130 | + async function testWriterEndWithPreAbortedSignal() { | ||
| 131 | + const [channelA, channelB] = duplex(); | ||
| 132 | + const reason = new Error('end aborted'); | ||
| 133 | + | ||
| 134 | + await assert.rejects( | ||
| 135 | + channelA.writer.end({ signal: AbortSignal.abort(reason) }), | ||
| 136 | + (error) => error === reason, | ||
| 137 | + ); | ||
| 138 | + | ||
| 139 | + await channelA.writer.write('still open'); | ||
| 140 | + const completedEnd = channelA.writer.end(); | ||
| 141 | + assert.strictEqual(await text(channelB.readable), 'still open'); | ||
| 142 | + assert.strictEqual(await completedEnd, 10); | ||
| 143 | + await channelB.close(); | ||
| 144 | + } | ||
| 145 | + | ||
| 130 | 146 | async function testEmptyDuplex() { | |
| 131 | 147 | const [channelA, channelB] = duplex(); | |
| 132 | 148 | ||
@@ -182,6 +198,7 @@ Promise.all([ | |||
| 182 | 198 | testWithOptions(), | |
| 183 | 199 | testPerChannelOptions(), | |
| 184 | 200 | testAbortSignal(), | |
| 201 | + testWriterEndWithPreAbortedSignal(), | ||
| 185 | 202 | testEmptyDuplex(), | |
| 186 | 203 | testChannelFail(), | |
| 187 | 204 | testAbortSignalBothChannels(), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -260,6 +260,40 @@ async function testEndAsyncReturnValue() { | |||
| 260 | 260 | await consume; | |
| 261 | 261 | } | |
| 262 | 262 | ||
| 263 | + async function testEndWithPreAbortedSignal() { | ||
| 264 | + const { writer, readable } = push(); | ||
| 265 | + const reason = new Error('end aborted'); | ||
| 266 | + | ||
| 267 | + writer.writeSync('hello'); | ||
| 268 | + await assert.rejects( | ||
| 269 | + writer.end({ signal: AbortSignal.abort(reason) }), | ||
| 270 | + (error) => error === reason, | ||
| 271 | + ); | ||
| 272 | + | ||
| 273 | + // A rejected end must leave the writer open. | ||
| 274 | + writer.writeSync(' world'); | ||
| 275 | + const consume = text(readable); | ||
| 276 | + assert.strictEqual(await writer.end(), 11); | ||
| 277 | + assert.strictEqual(await consume, 'hello world'); | ||
| 278 | + } | ||
| 279 | + | ||
| 280 | + async function testEndSignalAbortWhileDraining() { | ||
| 281 | + const { writer, readable } = push(); | ||
| 282 | + const controller = new AbortController(); | ||
| 283 | + const reason = new Error('end aborted while draining'); | ||
| 284 | + | ||
| 285 | + writer.writeSync('hello'); | ||
| 286 | + const abortedEnd = writer.end({ signal: controller.signal }); | ||
| 287 | + controller.abort(reason); | ||
| 288 | + | ||
| 289 | + await assert.rejects(abortedEnd, (error) => error === reason); | ||
| 290 | + | ||
| 291 | + // Aborting the operation does not undo the end-of-stream signal. | ||
| 292 | + const completedEnd = writer.end(); | ||
| 293 | + assert.strictEqual(await text(readable), 'hello'); | ||
| 294 | + assert.strictEqual(await completedEnd, 5); | ||
| 295 | + } | ||
| 296 | + | ||
| 263 | 297 | async function testEndAfterEndSyncWaitsForDrain() { | |
| 264 | 298 | const { writer, readable } = push(); | |
| 265 | 299 | writer.writeSync('hello'); | |
@@ -553,6 +587,8 @@ Promise.all([ | |||
| 553 | 587 | testOndrainProtocolErrorPropagates(), | |
| 554 | 588 | testFail(), | |
| 555 | 589 | testEndAsyncReturnValue(), | |
| 590 | + testEndWithPreAbortedSignal(), | ||
| 591 | + testEndSignalAbortWhileDraining(), | ||
| 556 | 592 | testEndAfterEndSyncWaitsForDrain(), | |
| 557 | 593 | testWriteUint8Array(), | |
| 558 | 594 | testOndrainWaitsForDrain(), | |
| Back | FazBrowse Home | New Git URL |
0 commit comments