@@ -73,6 +73,8 @@ ObjectSetPrototypeOf(Writable, Stream);
73
73
function nop ( ) { }
74
74
75
75
const kOnFinished = Symbol ( 'kOnFinished' ) ;
76
+ const kErrored = Symbol ( 'kErrored' ) ;
77
+ const kCorked = Symbol ( 'kCorked' ) ;
76
78
77
79
const kObjectMode = 1 << 0 ;
78
80
const kEnded = 1 << 1 ;
@@ -94,6 +96,12 @@ const kBufferProcessing = 1 << 16;
94
96
const kPrefinished = 1 << 17 ;
95
97
const kAllBuffers = 1 << 18 ;
96
98
const kAllNoop = 1 << 19 ;
99
+ const kHasOnFinished = 1 << 20 ;
100
+ const kHasErrored = 1 << 21 ;
101
+
102
+ const kCorkedShift = 22 ;
103
+ const kCorkedMask = 0b1111
104
+ const kCorked = kCorkedMask << kCorkedShift ; // 4 bits
97
105
98
106
// TODO(benjamingr) it is likely slower to do it this way than with free functions
99
107
function makeBitMapDescriptor ( bit ) {
@@ -176,6 +184,46 @@ ObjectDefineProperties(WritableState.prototype, {
176
184
177
185
allBuffers : makeBitMapDescriptor ( kAllBuffers ) ,
178
186
allNoop : makeBitMapDescriptor ( kAllNoop ) ,
187
+
188
+ // Indicates whether the stream has errored. When true all write() calls
189
+ // should return false. This is needed since when autoDestroy
190
+ // is disabled we need a way to tell whether the stream has failed.
191
+ // This is/should be a cold path.
192
+ errored : {
193
+ enumerable : false ,
194
+ get ( ) { return ( this . state & kHasErrored ) !== 0 ? this [ kErrored ] : null ; } ,
195
+ set ( value ) {
196
+ if ( value ) {
197
+ this [ kErrored ] = value ;
198
+ this . state |= kHasErrored ;
199
+ } else {
200
+ delete this [ kErrored ] ;
201
+ this . state &= ~ kHasErrored ;
202
+ }
203
+ } ,
204
+ } ,
205
+
206
+ // When true all writes will be buffered until .uncork() call.
207
+ // This is/should be a cold path.
208
+ corked : {
209
+ enumerable : false ,
210
+ get ( ) {
211
+ const val = ( this . state >>> kCorkedShift ) & kCorkedMask ;
212
+ return val < kCorkedMask ? val : this [ kCorked ] ;
213
+ } ,
214
+ set ( value ) {
215
+ if ( value >= kCorkedMask ) {
216
+ this [ kCorked ] = value ;
217
+ this . state |= kCorkedMask << kCorkedShift ;
218
+ } else {
219
+ if ( ( this . state >>> kCorkedShift ) === kCorkedMask ) {
220
+ delete this [ kCorked ] ;
221
+ }
222
+ this . state &= ~ kCorked ;
223
+ this . state |= value << kCorkedShift ;
224
+ }
225
+ } ,
226
+ }
179
227
} ) ;
180
228
181
229
function WritableState ( options , stream , isDuplex ) {
@@ -226,9 +274,6 @@ function WritableState(options, stream, isDuplex) {
226
274
// socket or file.
227
275
this . length = 0 ;
228
276
229
- // When true all writes will be buffered until .uncork() call.
230
- this . corked = 0 ;
231
-
232
277
// The callback that's passed to _write(chunk, cb).
233
278
this . onwrite = onwrite . bind ( undefined , stream ) ;
234
279
@@ -247,13 +292,6 @@ function WritableState(options, stream, isDuplex) {
247
292
// Number of pending user-supplied write callbacks
248
293
// this must be 0 before 'finish' can be emitted.
249
294
this . pendingcb = 0 ;
250
-
251
- // Indicates whether the stream has errored. When true all write() calls
252
- // should return false. This is needed since when autoDestroy
253
- // is disabled we need a way to tell whether the stream has failed.
254
- this . errored = null ;
255
-
256
- this [ kOnFinished ] = [ ] ;
257
295
}
258
296
259
297
function resetBuffer ( state ) {
@@ -394,13 +432,21 @@ Writable.prototype.write = function(chunk, encoding, cb) {
394
432
} ;
395
433
396
434
Writable . prototype . cork = function ( ) {
397
- this . _writableState . corked ++ ;
435
+ const state = this . _writableState ;
436
+
437
+ const corked = ( ( state & kCorked ) >>> kCorkedShift ) + 1 ;
438
+ if ( corked < kCorkedMask ) {
439
+ this . state |= corked << kCorkedShift ;
440
+ } else {
441
+ this . _writableState . corked ++ ;
442
+ }
398
443
} ;
399
444
400
445
Writable . prototype . uncork = function ( ) {
401
446
const state = this . _writableState ;
402
447
403
- if ( state . corked ) {
448
+ if ( ( state . state & kCorked ) !== 0 ) {
449
+ // TODO: Optimize
404
450
state . corked -- ;
405
451
406
452
if ( ( state . state & kWriting ) === 0 )
@@ -432,7 +478,7 @@ function writeOrBuffer(stream, state, chunk, encoding, callback) {
432
478
if ( ! ret )
433
479
state . state |= kNeedDrain ;
434
480
435
- if ( ( state . state & kWriting ) !== 0 || state . corked || state . errored || ( state . state & kConstructed ) === 0 ) {
481
+ if ( ( state . state & kWriting ) !== 0 || ( state . state & ( kHasErrored | kCorked ) ) || ( state . state & kConstructed ) === 0 ) {
436
482
state . buffered . push ( { chunk, encoding, callback } ) ;
437
483
if ( ( state . state & kAllBuffers ) !== 0 && encoding !== 'buffer' ) {
438
484
state . state &= ~ kAllBuffers ;
@@ -450,7 +496,7 @@ function writeOrBuffer(stream, state, chunk, encoding, callback) {
450
496
451
497
// Return false if errored or destroyed in order to break
452
498
// any synchronous while(stream.write(data)) loops.
453
- return ret && ! state . errored && ( state . state & kDestroyed ) === 0 ;
499
+ return ret && ( state . state & kHasErrored ) === 0 && ( state . state & kDestroyed ) === 0 ;
454
500
}
455
501
456
502
function doWrite ( stream , state , writev , len , chunk , encoding , cb ) {
@@ -498,7 +544,7 @@ function onwrite(stream, er) {
498
544
// Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364
499
545
er . stack ; // eslint-disable-line no-unused-expressions
500
546
501
- if ( ! state . errored ) {
547
+ if ( ( state . state & kHasErrored ) === 0 ) {
502
548
state . errored = er ;
503
549
}
504
550
@@ -573,18 +619,19 @@ function errorBuffer(state) {
573
619
callback ( state . errored ?? new ERR_STREAM_DESTROYED ( 'write' ) ) ;
574
620
}
575
621
576
- const onfinishCallbacks = state [ kOnFinished ] . splice ( 0 ) ;
577
- for ( let i = 0 ; i < onfinishCallbacks . length ; i ++ ) {
578
- onfinishCallbacks [ i ] ( state . errored ?? new ERR_STREAM_DESTROYED ( 'end' ) ) ;
622
+ if ( ( state . state & kHasOnFinished ) !== 0 ) {
623
+ const onfinishCallbacks = state [ kOnFinished ] . splice ( 0 ) ;
624
+ for ( let i = 0 ; i < onfinishCallbacks . length ; i ++ ) {
625
+ onfinishCallbacks [ i ] ( state . errored ?? new ERR_STREAM_DESTROYED ( 'end' ) ) ;
626
+ }
579
627
}
580
628
581
629
resetBuffer ( state ) ;
582
630
}
583
631
584
632
// If there's something in the buffer waiting, then process it.
585
633
function clearBuffer ( stream , state ) {
586
- if ( state . corked ||
587
- ( state . state & ( kDestroyed | kBufferProcessing ) ) !== 0 ||
634
+ if ( ( state . state & ( kDestroyed | kBufferProcessing | kCorked ) ) !== 0 ||
588
635
( state . state & kConstructed ) === 0 ) {
589
636
return ;
590
637
}
@@ -669,14 +716,14 @@ Writable.prototype.end = function(chunk, encoding, cb) {
669
716
}
670
717
671
718
// .end() fully uncorks.
672
- if ( state . corked ) {
719
+ if ( ( state . state & kCorked ) !== 0 ) {
673
720
state . corked = 1 ;
674
721
this . uncork ( ) ;
675
722
}
676
723
677
724
if ( err ) {
678
725
// Do nothing...
679
- } else if ( ! state . errored && ( state . state & kEnding ) === 0 ) {
726
+ } else if ( ( state . state & kErrored ) === 0 && ( state . state & kEnding ) === 0 ) {
680
727
// This is forgiving in terms of unnecessary calls to end() and can hide
681
728
// logic errors. However, usually such errors are harmless and causing a
682
729
// hard error can be disproportionately destructive. It is not always
@@ -698,6 +745,8 @@ Writable.prototype.end = function(chunk, encoding, cb) {
698
745
} else if ( ( state . state & kFinished ) !== 0 ) {
699
746
process . nextTick ( cb , null ) ;
700
747
} else {
748
+ state . state |= kHasOnFinished ;
749
+ state [ kOnFinished ] ??= [ ] ;
701
750
state [ kOnFinished ] . push ( cb ) ;
702
751
}
703
752
}
@@ -715,10 +764,10 @@ function needFinish(state) {
715
764
kFinished |
716
765
kWriting |
717
766
kErrorEmitted |
718
- kCloseEmitted
767
+ kCloseEmitted |
768
+ kHasErrored
719
769
) ) === ( kEnding | kConstructed ) &&
720
770
state . length === 0 &&
721
- ! state . errored &&
722
771
state . buffered . length === 0 ) ;
723
772
}
724
773
@@ -734,9 +783,11 @@ function callFinal(stream, state) {
734
783
735
784
state . pendingcb -- ;
736
785
if ( err ) {
737
- const onfinishCallbacks = state [ kOnFinished ] . splice ( 0 ) ;
738
- for ( let i = 0 ; i < onfinishCallbacks . length ; i ++ ) {
739
- onfinishCallbacks [ i ] ( err ) ;
786
+ if ( ( state . state & kHasOnFinished ) !== 0 ) {
787
+ const onfinishCallbacks = state [ kOnFinished ] . splice ( 0 ) ;
788
+ for ( let i = 0 ; i < onfinishCallbacks . length ; i ++ ) {
789
+ onfinishCallbacks [ i ] ( err ) ;
790
+ }
740
791
}
741
792
errorOrDestroy ( stream , err , ( state . state & kSync ) !== 0 ) ;
742
793
} else if ( needFinish ( state ) ) {
@@ -799,9 +850,11 @@ function finish(stream, state) {
799
850
state . pendingcb -- ;
800
851
state . state |= kFinished ;
801
852
802
- const onfinishCallbacks = state [ kOnFinished ] . splice ( 0 ) ;
803
- for ( let i = 0 ; i < onfinishCallbacks . length ; i ++ ) {
804
- onfinishCallbacks [ i ] ( null ) ;
853
+ if ( ( state . state & kHasOnFinished ) !== 0 ) {
854
+ const onfinishCallbacks = state [ kOnFinished ] . splice ( 0 ) ;
855
+ for ( let i = 0 ; i < onfinishCallbacks . length ; i ++ ) {
856
+ onfinishCallbacks [ i ] ( null ) ;
857
+ }
805
858
}
806
859
807
860
stream . emit ( 'finish' ) ;
@@ -853,8 +906,8 @@ ObjectDefineProperties(Writable.prototype, {
853
906
// where the writable side was disabled upon construction.
854
907
// Compat. The user might manually disable writable side through
855
908
// deprecated setter.
856
- return ! ! w && w . writable !== false && ! w . errored &&
857
- ( w . state & ( kEnding | kEnded | kDestroyed ) ) === 0 ;
909
+ return ! ! w && w . writable !== false &&
910
+ ( w . state & ( kEnding | kEnded | kDestroyed | kHasErrored ) ) === 0 ;
858
911
} ,
859
912
set ( val ) {
860
913
// Backwards compatible.
@@ -928,7 +981,7 @@ ObjectDefineProperties(Writable.prototype, {
928
981
__proto__ : null ,
929
982
enumerable : false ,
930
983
get ( ) {
931
- return this . _writableState ? this . _writableState . errored : null ;
984
+ return this . _writableState && ( this . _writableState . state & kHasErrored ) !== 0 ? this . _writableState . errored : null ;
932
985
} ,
933
986
} ,
934
987
@@ -938,7 +991,7 @@ ObjectDefineProperties(Writable.prototype, {
938
991
get : function ( ) {
939
992
return ! ! (
940
993
this . _writableState . writable !== false &&
941
- ( ( this . _writableState . state & kDestroyed ) !== 0 || this . _writableState . errored ) &&
994
+ ( this . _writableState . state & ( kDestroyed | kHasErrored ) ) !== 0 &&
942
995
( this . _writableState . state & kFinished ) === 0
943
996
) ;
944
997
} ,
@@ -952,7 +1005,7 @@ Writable.prototype.destroy = function(err, cb) {
952
1005
// Invoke pending callbacks.
953
1006
if ( ( state . state & kDestroyed ) === 0 &&
954
1007
( state . bufferedIndex < state . buffered . length ||
955
- state [ kOnFinished ] . length ) ) {
1008
+ ( ( ( state . state & kHasOnFinished ) !== 0 ) && state [ kOnFinished ] . length ) ) ) {
956
1009
process . nextTick ( errorBuffer , state ) ;
957
1010
}
958
1011
0 commit comments