@@ -14,6 +14,8 @@ const {
1414 PromiseReject,
1515 PromiseResolve,
1616 PromiseWithResolvers,
17+ SafePromisePrototypeFinally,
18+ SafePromiseRace,
1719 SafeSet,
1820 Symbol,
1921 SymbolAsyncDispose,
@@ -77,6 +79,22 @@ const kEnd = Symbol('kEnd');
7779const kAbort = Symbol ( 'kAbort' ) ;
7880const kCanWrite = Symbol ( 'kCanWrite' ) ;
7981const kOnBufferDrained = Symbol ( 'kOnBufferDrained' ) ;
82+ const kOnEndDrained = Symbol ( 'kOnEndDrained' ) ;
83+ const kPendingWriteRemoved = Symbol ( 'kPendingWriteRemoved' ) ;
84+
85+ function raceEndWithSignal ( promise , signal ) {
86+ if ( ! signal ) return promise ;
87+
88+ const { promise : aborted , reject } = PromiseWithResolvers ( ) ;
89+ const onAbort = ( ) => reject ( signal . reason ) ;
90+ signal . addEventListener ( 'abort' , onAbort , { __proto__ : null , once : true } ) ;
91+ if ( signal . aborted ) onAbort ( ) ;
92+
93+ return SafePromisePrototypeFinally (
94+ SafePromiseRace ( [ promise , aborted ] ) ,
95+ ( ) => signal . removeEventListener ( 'abort' , onAbort ) ,
96+ ) ;
97+ }
8098
8199// =============================================================================
82100// Broadcast Implementation
@@ -100,6 +118,7 @@ class BroadcastImpl {
100118 constructor ( options ) {
101119 this . #options = options ;
102120 this [ kOnBufferDrained ] = null ;
121+ this [ kOnEndDrained ] = null ;
103122 }
104123
105124 setWriter ( writer ) {
@@ -183,6 +202,7 @@ class BroadcastImpl {
183202 if ( self . #deleteConsumer( state ) ) {
184203 self . #tryTrimBuffer( ) ;
185204 }
205+ self . #notifyEndDrained( ) ;
186206 }
187207
188208 return {
@@ -358,10 +378,11 @@ class BroadcastImpl {
358378 }
359379 }
360380 }
381+ this . #notifyEndDrained( ) ;
361382 }
362383
363384 [ kAbort ] ( reason ) {
364- if ( this . #ended || this . # error !== undefined ) return ;
385+ if ( this . #error !== undefined ) return ;
365386 this . #error = reason ;
366387 this . #ended = true ;
367388
@@ -396,6 +417,12 @@ class BroadcastImpl {
396417
397418 // Private methods
398419
420+ #notifyEndDrained( ) {
421+ if ( this . #ended && this . #consumers. size === 0 ) {
422+ this [ kOnEndDrained ] ?. ( ) ;
423+ }
424+ }
425+
399426 #recomputeMinCursor( ) {
400427 const { minCursor, minCursorConsumers } = getMinCursor (
401428 this . #consumers, this . #bufferStart + this . #buffer. length ) ;
@@ -516,8 +543,9 @@ let getBroadcastPendingWrites;
516543class BroadcastWriter {
517544 #broadcast;
518545 #totalBytes = 0 ;
519- #closed;
520- #aborted = false ;
546+ #state = 'open' ;
547+ #error;
548+ #pendingEnd;
521549 #pendingWrites = new RingBuffer ( ) ;
522550 #pendingDrains = [ ] ;
523551
@@ -532,8 +560,11 @@ class BroadcastWriter {
532560
533561 this . #broadcast[ kOnBufferDrained ] = ( ) => {
534562 this . #resolvePendingWrites( ) ;
535- this . #resolvePendingDrains( true ) ;
563+ if ( this . #state === 'open' ) {
564+ this . #resolvePendingDrains( true ) ;
565+ }
536566 } ;
567+ this . #broadcast[ kOnEndDrained ] = ( ) => this . #endDrained( ) ;
537568 }
538569
539570 // The drainable protocol works with Stream.ondrain to provide a notification
@@ -547,20 +578,12 @@ class BroadcastWriter {
547578 return promise ;
548579 }
549580
550- #isClosed( ) {
551- return this . #closed !== undefined ;
552- }
553-
554- #isClosedOrAborted( ) {
555- return this . #isClosed( ) || this . #aborted;
556- }
557-
558581 get canWrite ( ) {
559- return this . #isClosedOrAborted ( ) ? null : this . #broadcast[ kCanWrite ] ( ) ;
582+ return this . #state === 'open' ? this . #broadcast[ kCanWrite ] ( ) : null ;
560583 }
561584
562585 #canUseWriteFastPath( signal ) {
563- return ! signal && ! this . #isClosed ( ) && ! this . #aborted &&
586+ return ! signal && this . #state === 'open' &&
564587 this . #broadcast[ kCanWrite ] ( ) ;
565588 }
566589
@@ -592,13 +615,15 @@ class BroadcastWriter {
592615 }
593616
594617 async #writevSlow( chunks , signal ) {
595- // Check for pre-aborted
596- signal ?. throwIfAborted ( ) ;
597-
598- if ( this . #isClosedOrAborted ( ) ) {
618+ if ( this . #state === 'errored' ) {
619+ throw this . #error ;
620+ }
621+ if ( this . #state !== 'open' ) {
599622 throw new ERR_INVALID_STATE . TypeError ( 'Writer is closed' ) ;
600623 }
601624
625+ signal ?. throwIfAborted ( ) ;
626+
602627 const converted = convertChunks ( chunks ) ;
603628
604629 if ( this . #broadcast[ kWrite ] ( converted ) ) {
@@ -624,7 +649,7 @@ class BroadcastWriter {
624649 }
625650
626651 writeSync ( chunk ) {
627- if ( this . #isClosedOrAborted ( ) ) return false ;
652+ if ( this . #state !== 'open' ) return false ;
628653 if ( ! this . #broadcast[ kCanWrite ] ( ) ) return false ;
629654 const converted =
630655 toUint8Array ( chunk ) ;
@@ -637,7 +662,7 @@ class BroadcastWriter {
637662
638663 writevSync ( chunks ) {
639664 validateArray ( chunks , 'chunks' ) ;
640- if ( this . #isClosedOrAborted ( ) ) return false ;
665+ if ( this . #state !== 'open' ) return false ;
641666 if ( ! this . #broadcast[ kCanWrite ] ( ) ) return false ;
642667 const converted = convertChunks ( chunks ) ;
643668 if ( this . #broadcast[ kWrite ] ( converted ) ) {
@@ -651,34 +676,43 @@ class BroadcastWriter {
651676
652677 end ( options ) {
653678 const signal = getWriterSignal ( options ) ;
679+ if ( this . #state === 'errored' ) return PromiseReject ( this . #error) ;
680+ if ( this . #state === 'closed' ) return PromiseResolve ( this . #totalBytes) ;
654681 if ( signal ?. aborted ) return PromiseReject ( signal . reason ) ;
655682
656- if ( this . #isClosed( ) ) return this . #closed;
657- this . #closed = PromiseResolve ( this . #totalBytes) ;
658- this . #broadcast[ kEnd ] ( ) ;
659- this . #resolvePendingDrains( false ) ;
660- return this . #closed;
683+ const endPromise = this . #getEndPromise( ) ;
684+ if ( this . #state === 'open' ) {
685+ this . #state = 'closing' ;
686+ this . #resolvePendingDrains( false ) ;
687+ this . #finishEndIfReady( ) ;
688+ }
689+
690+ return raceEndWithSignal ( endPromise , signal ) ;
661691 }
662692
663693 endSync ( ) {
664- if ( this . #closed) return this . #totalBytes;
665- this . #closed = PromiseResolve ( this . #totalBytes) ;
666- this . #broadcast[ kEnd ] ( ) ;
694+ if ( this . #state === 'closed' ) return this . #totalBytes;
695+ if ( this . #state === 'errored' || this . #state === 'closing' ) return - 1 ;
696+
697+ this . #state = 'closing' ;
667698 this . #resolvePendingDrains( false ) ;
668- return this . #totalBytes;
699+ this . #finishEndIfReady( ) ;
700+ return this . #state === 'closed' ? this . #totalBytes : - 1 ;
669701 }
670702
671703 fail ( reason ) {
672- if ( this . #isClosedOrAborted( ) ) return ;
673- this . #aborted = true ;
674- this . #closed = PromiseResolve ( this . #totalBytes) ;
704+ if ( this . #state === 'errored' || this . #state === 'closed' ) return ;
705+ this . #state = 'errored' ;
675706 const error = reason ?? new ERR_INVALID_STATE . TypeError ( 'Failed' ) ;
707+ this . #error = error ;
676708 this . #rejectPendingWrites( error ) ;
677709 this . #rejectPendingDrains( error ) ;
710+ this . #pendingEnd?. reject ( error ) ;
678711 this . #broadcast[ kAbort ] ( error ) ;
679712 }
680713
681714 [ SymbolAsyncDispose ] ( ) {
715+ if ( this . #state === 'closing' ) return this . #getEndPromise( ) ;
682716 this . fail ( ) ;
683717 return PromiseResolve ( ) ;
684718 }
@@ -688,11 +722,33 @@ class BroadcastWriter {
688722 }
689723
690724 [ kCancelWriter ] ( ) {
691- if ( this . #isClosed ( ) ) return ;
692- this . #closed = PromiseResolve ( this . #totalBytes ) ;
725+ if ( this . #state === 'closed' || this . #state === 'errored' ) return ;
726+ this . #state = 'closed' ;
693727 this . #rejectPendingWrites(
694728 lazyDOMException ( 'Broadcast cancelled' , 'AbortError' ) ) ;
695729 this . #resolvePendingDrains( false ) ;
730+ this . #pendingEnd?. resolve ( this . #totalBytes) ;
731+ }
732+
733+ #getEndPromise( ) {
734+ this . #pendingEnd ??= PromiseWithResolvers ( ) ;
735+ return this . #pendingEnd. promise ;
736+ }
737+
738+ #finishEndIfReady( ) {
739+ if ( this . #state === 'closing' && this . #pendingWrites. length === 0 ) {
740+ this . #broadcast[ kEnd ] ( ) ;
741+ }
742+ }
743+
744+ #endDrained( ) {
745+ if ( this . #state !== 'closing' ) return ;
746+ this . #state = 'closed' ;
747+ this . #pendingEnd?. resolve ( this . #totalBytes) ;
748+ }
749+
750+ [ kPendingWriteRemoved ] ( ) {
751+ this . #finishEndIfReady( ) ;
696752 }
697753
698754 /**
@@ -724,6 +780,7 @@ class BroadcastWriter {
724780 break ;
725781 }
726782 }
783+ this . #finishEndIfReady( ) ;
727784 }
728785
729786 #rejectPendingWrites( error ) {
@@ -756,6 +813,7 @@ function wireBroadcastWriteSignal(entry, signal, resolve, reject, self) {
756813 if ( idx !== - 1 ) pendingWrites . removeAt ( idx ) ;
757814 entry . chunk = null ;
758815 reject ( signal . reason ?? lazyDOMException ( 'Aborted' , 'AbortError' ) ) ;
816+ if ( idx !== - 1 ) self [ kPendingWriteRemoved ] ( ) ;
759817 } ;
760818 entry . resolve = function ( ) {
761819 signal . removeEventListener ( 'abort' , onAbort ) ;
0 commit comments