Commit 5658c63

Browse files
ikeyanaduh95
authored andcommitted
stream: align Readable.toWeb termination with eos
PR-URL: #62394 Backport-PR-URL: #64675 Reviewed-By: Mattias Buelens <mattias@buelens.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cc425bc commit 5658c63

5 files changed

Lines changed: 416 additions & 164 deletions

File tree

β€Žlib/internal/streams/end-of-stream.jsβ€Ž

Lines changed: 141 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
const{
77
Promise,
88
PromisePrototypeThen,
9+
ReflectApply,
10+
Symbol,
911
SymbolDispose,
1012
}=primordials;
1113

@@ -62,6 +64,50 @@ function bindAsyncResource(fn, type) {
6264
};
6365
}
6466

67+
/**
68+
* Returns the current stream error tracked by eos(), if any.
69+
* @param {import('stream').Stream} stream
70+
* @returns {Error | null}
71+
*/
72+
functiongetEosErrored(stream){
73+
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
74+
returntypeoferrored!=='boolean'&&errored||null;
75+
}
76+
77+
/**
78+
* Returns the error eos() would report from an immediate close, including
79+
* premature close detection for unfinished readable or writable sides.
80+
* @param {import('stream').Stream} stream
81+
* @param {boolean} readable
82+
* @param {boolean | null} readableFinished
83+
* @param {boolean} writable
84+
* @param {boolean | null} writableFinished
85+
* @returns {Error | null}
86+
*/
87+
functiongetEosOnCloseError(stream,readable,readableFinished,writable,writableFinished){
88+
consterrored=getEosErrored(stream);
89+
if(errored){
90+
returnerrored;
91+
}
92+
93+
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
94+
if(!isReadableFinished(stream,false)){
95+
returnnewERR_STREAM_PREMATURE_CLOSE();
96+
}
97+
}
98+
if(writable&&!writableFinished){
99+
if(!isWritableFinished(stream,false)){
100+
returnnewERR_STREAM_PREMATURE_CLOSE();
101+
}
102+
}
103+
104+
returnnull;
105+
}
106+
107+
// Internal only: if eos() can settle immediately, invoke the callback before
108+
// returning cleanup. Callers must tolerate cleanup yet to be assigned.
109+
constkEosNodeSynchronousCallback=Symbol('kEosNodeSynchronousCallback');
110+
65111
functioneos(stream,options,callback){
66112
if(arguments.length===2){
67113
callback=options;
@@ -74,10 +120,6 @@ function eos(stream, options, callback) {
74120
validateFunction(callback,'callback');
75121
validateAbortSignal(options.signal,'options.signal');
76122

77-
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
78-
// is a bottleneck here.
79-
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
80-
81123
if(isReadableStream(stream)||isWritableStream(stream)){
82124
returneosWeb(stream,options,callback);
83125
}
@@ -89,15 +131,6 @@ function eos(stream, options, callback) {
89131
constreadable=options.readable??isReadableNodeStream(stream);
90132
constwritable=options.writable??isWritableNodeStream(stream);
91133

92-
constwState=stream._writableState;
93-
constrState=stream._readableState;
94-
95-
constonlegacyfinish=()=>{
96-
if(!stream.writable){
97-
onfinish();
98-
}
99-
};
100-
101134
// TODO (ronag): Improve soft detection to include core modules and
102135
// common ecosystem modules that do properly emit 'close' but fail
103136
// this generic check.
@@ -106,8 +139,83 @@ function eos(stream, options, callback) {
106139
isReadableNodeStream(stream)===readable&&
107140
isWritableNodeStream(stream)===writable
108141
);
109-
110142
letwritableFinished=isWritableFinished(stream,false);
143+
letreadableFinished=isReadableFinished(stream,false);
144+
145+
constwState=stream._writableState;
146+
constrState=stream._readableState;
147+
148+
/**
149+
* @type {Error | null | undefined}
150+
* undefined: to be determined
151+
* null: no error
152+
* Error: an error occurred
153+
*/
154+
letimmediateResult;
155+
if(isClosed(stream)){
156+
immediateResult=getEosOnCloseError(
157+
stream,
158+
readable,
159+
readableFinished,
160+
writable,
161+
writableFinished,
162+
);
163+
}elseif(wState?.errorEmitted||rState?.errorEmitted){
164+
if(!willEmitClose){
165+
immediateResult=getEosErrored(stream);
166+
}
167+
}elseif(
168+
!readable&&
169+
(!willEmitClose||isReadable(stream))&&
170+
(writableFinished||isWritable(stream)===false)&&
171+
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
172+
){
173+
immediateResult=getEosErrored(stream);
174+
}elseif(
175+
!writable&&
176+
(!willEmitClose||isWritable(stream))&&
177+
(readableFinished||isReadable(stream)===false)
178+
){
179+
immediateResult=getEosErrored(stream);
180+
}elseif((rState&&stream.req&&stream.aborted)){
181+
immediateResult=getEosErrored(stream);
182+
}
183+
letcleanup=()=>{
184+
callback=nop;
185+
};
186+
if(immediateResult!==undefined){
187+
if(options.error!==false){
188+
stream.on('error',nop);
189+
cleanup=()=>{
190+
callback=nop;
191+
stream.removeListener('error',nop);
192+
};
193+
}
194+
}elseif(options.signal?.aborted){
195+
immediateResult=newAbortError(undefined,{cause: options.signal.reason});
196+
}
197+
if(immediateResult!==undefined&&options[kEosNodeSynchronousCallback]){
198+
ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]);
199+
returncleanup;
200+
}
201+
202+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
203+
// is a bottleneck here.
204+
callback=bindAsyncResource(callback,'STREAM_END_OF_STREAM');
205+
206+
if(immediateResult!==undefined){
207+
process.nextTick(()=>ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]));
208+
returncleanup;
209+
}
210+
211+
callback=once(callback);
212+
213+
constonlegacyfinish=()=>{
214+
if(!stream.writable){
215+
onfinish();
216+
}
217+
};
218+
111219
constonfinish=()=>{
112220
writableFinished=true;
113221
// Stream should not be destroyed here. If it is that
@@ -126,7 +234,6 @@ function eos(stream, options, callback) {
126234
}
127235
};
128236

129-
letreadableFinished=isReadableFinished(stream,false);
130237
constonend=()=>{
131238
readableFinished=true;
132239
// Stream should not be destroyed here. If it is that
@@ -149,41 +256,13 @@ function eos(stream, options, callback) {
149256
callback.call(stream,err);
150257
};
151258

152-
letclosed=isClosed(stream);
153-
154259
constonclose=()=>{
155-
closed=true;
156-
157-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
158-
159-
if(errored&&typeoferrored!=='boolean'){
160-
returncallback.call(stream,errored);
161-
}
162-
163-
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
164-
if(!isReadableFinished(stream,false))
165-
returncallback.call(stream,
166-
newERR_STREAM_PREMATURE_CLOSE());
167-
}
168-
if(writable&&!writableFinished){
169-
if(!isWritableFinished(stream,false))
170-
returncallback.call(stream,
171-
newERR_STREAM_PREMATURE_CLOSE());
172-
}
173-
174-
callback.call(stream);
175-
};
176-
177-
constonclosed=()=>{
178-
closed=true;
179-
180-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
181-
182-
if(errored&&typeoferrored!=='boolean'){
183-
returncallback.call(stream,errored);
260+
consterror=getEosOnCloseError(stream,readable,readableFinished,writable,writableFinished);
261+
if(error===null){
262+
callback.call(stream);
263+
}else{
264+
callback.call(stream,error);
184265
}
185-
186-
callback.call(stream);
187266
};
188267

189268
constonrequest=()=>{
@@ -217,30 +296,7 @@ function eos(stream, options, callback) {
217296
}
218297
stream.on('close',onclose);
219298

220-
if(closed){
221-
process.nextTick(onclose);
222-
}elseif(wState?.errorEmitted||rState?.errorEmitted){
223-
if(!willEmitClose){
224-
process.nextTick(onclosed);
225-
}
226-
}elseif(
227-
!readable&&
228-
(!willEmitClose||isReadable(stream))&&
229-
(writableFinished||isWritable(stream)===false)&&
230-
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
231-
){
232-
process.nextTick(onclosed);
233-
}elseif(
234-
!writable&&
235-
(!willEmitClose||isWritable(stream))&&
236-
(readableFinished||isReadable(stream)===false)
237-
){
238-
process.nextTick(onclosed);
239-
}elseif((rState&&stream.req&&stream.aborted)){
240-
process.nextTick(onclosed);
241-
}
242-
243-
constcleanup=()=>{
299+
cleanup=()=>{
244300
callback=nop;
245301
stream.removeListener('aborted',onclose);
246302
stream.removeListener('complete',onfinish);
@@ -255,7 +311,7 @@ function eos(stream, options, callback) {
255311
stream.removeListener('close',onclose);
256312
};
257313

258-
if(options.signal&&!closed){
314+
if(options.signal){
259315
constabort=()=>{
260316
// Keep it because cleanup removes it.
261317
constendCallback=callback;
@@ -264,23 +320,23 @@ function eos(stream, options, callback) {
264320
stream,
265321
newAbortError(undefined,{cause: options.signal.reason}));
266322
};
267-
if(options.signal.aborted){
268-
process.nextTick(abort);
269-
}else{
270-
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
271-
constdisposable=addAbortListener(options.signal,abort);
272-
constoriginalCallback=callback;
273-
callback=once((...args)=>{
274-
disposable[SymbolDispose]();
275-
originalCallback.apply(stream,args);
276-
});
277-
}
323+
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
324+
constdisposable=addAbortListener(options.signal,abort);
325+
constoriginalCallback=callback;
326+
callback=once((...args)=>{
327+
disposable[SymbolDispose]();
328+
ReflectApply(originalCallback,stream,args);
329+
});
278330
}
279331

280332
returncleanup;
281333
}
282334

283335
functioneosWeb(stream,options,callback){
336+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
337+
// is a bottleneck here.
338+
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
339+
284340
letisAborted=false;
285341
letabort=nop;
286342
if(options.signal){
@@ -339,4 +395,5 @@ function finished(stream, opts) {
339395
module.exports={
340396
eos,
341397
finished,
398+
kEosNodeSynchronousCallback,
342399
};

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 5658c63

Browse files
ikeyanaduh95
authored andcommitted
stream: align Readable.toWeb termination with eos
PR-URL: #62394 Backport-PR-URL: #64675 Reviewed-By: Mattias Buelens <mattias@buelens.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cc425bc commit 5658c63

5 files changed

Lines changed: 416 additions & 164 deletions

File tree

β€Žlib/internal/streams/end-of-stream.jsβ€Ž

Lines changed: 141 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
const{
77
Promise,
88
PromisePrototypeThen,
9+
ReflectApply,
10+
Symbol,
911
SymbolDispose,
1012
}=primordials;
1113

@@ -62,6 +64,50 @@ function bindAsyncResource(fn, type) {
6264
};
6365
}
6466

67+
/**
68+
* Returns the current stream error tracked by eos(), if any.
69+
* @param {import('stream').Stream} stream
70+
* @returns {Error | null}
71+
*/
72+
functiongetEosErrored(stream){
73+
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
74+
returntypeoferrored!=='boolean'&&errored||null;
75+
}
76+
77+
/**
78+
* Returns the error eos() would report from an immediate close, including
79+
* premature close detection for unfinished readable or writable sides.
80+
* @param {import('stream').Stream} stream
81+
* @param {boolean} readable
82+
* @param {boolean | null} readableFinished
83+
* @param {boolean} writable
84+
* @param {boolean | null} writableFinished
85+
* @returns {Error | null}
86+
*/
87+
functiongetEosOnCloseError(stream,readable,readableFinished,writable,writableFinished){
88+
consterrored=getEosErrored(stream);
89+
if(errored){
90+
returnerrored;
91+
}
92+
93+
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
94+
if(!isReadableFinished(stream,false)){
95+
returnnewERR_STREAM_PREMATURE_CLOSE();
96+
}
97+
}
98+
if(writable&&!writableFinished){
99+
if(!isWritableFinished(stream,false)){
100+
returnnewERR_STREAM_PREMATURE_CLOSE();
101+
}
102+
}
103+
104+
returnnull;
105+
}
106+
107+
// Internal only: if eos() can settle immediately, invoke the callback before
108+
// returning cleanup. Callers must tolerate cleanup yet to be assigned.
109+
constkEosNodeSynchronousCallback=Symbol('kEosNodeSynchronousCallback');
110+
65111
functioneos(stream,options,callback){
66112
if(arguments.length===2){
67113
callback=options;
@@ -74,10 +120,6 @@ function eos(stream, options, callback) {
74120
validateFunction(callback,'callback');
75121
validateAbortSignal(options.signal,'options.signal');
76122

77-
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
78-
// is a bottleneck here.
79-
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
80-
81123
if(isReadableStream(stream)||isWritableStream(stream)){
82124
returneosWeb(stream,options,callback);
83125
}
@@ -89,15 +131,6 @@ function eos(stream, options, callback) {
89131
constreadable=options.readable??isReadableNodeStream(stream);
90132
constwritable=options.writable??isWritableNodeStream(stream);
91133

92-
constwState=stream._writableState;
93-
constrState=stream._readableState;
94-
95-
constonlegacyfinish=()=>{
96-
if(!stream.writable){
97-
onfinish();
98-
}
99-
};
100-
101134
// TODO (ronag): Improve soft detection to include core modules and
102135
// common ecosystem modules that do properly emit 'close' but fail
103136
// this generic check.
@@ -106,8 +139,83 @@ function eos(stream, options, callback) {
106139
isReadableNodeStream(stream)===readable&&
107140
isWritableNodeStream(stream)===writable
108141
);
109-
110142
letwritableFinished=isWritableFinished(stream,false);
143+
letreadableFinished=isReadableFinished(stream,false);
144+
145+
constwState=stream._writableState;
146+
constrState=stream._readableState;
147+
148+
/**
149+
* @type {Error | null | undefined}
150+
* undefined: to be determined
151+
* null: no error
152+
* Error: an error occurred
153+
*/
154+
letimmediateResult;
155+
if(isClosed(stream)){
156+
immediateResult=getEosOnCloseError(
157+
stream,
158+
readable,
159+
readableFinished,
160+
writable,
161+
writableFinished,
162+
);
163+
}elseif(wState?.errorEmitted||rState?.errorEmitted){
164+
if(!willEmitClose){
165+
immediateResult=getEosErrored(stream);
166+
}
167+
}elseif(
168+
!readable&&
169+
(!willEmitClose||isReadable(stream))&&
170+
(writableFinished||isWritable(stream)===false)&&
171+
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
172+
){
173+
immediateResult=getEosErrored(stream);
174+
}elseif(
175+
!writable&&
176+
(!willEmitClose||isWritable(stream))&&
177+
(readableFinished||isReadable(stream)===false)
178+
){
179+
immediateResult=getEosErrored(stream);
180+
}elseif((rState&&stream.req&&stream.aborted)){
181+
immediateResult=getEosErrored(stream);
182+
}
183+
letcleanup=()=>{
184+
callback=nop;
185+
};
186+
if(immediateResult!==undefined){
187+
if(options.error!==false){
188+
stream.on('error',nop);
189+
cleanup=()=>{
190+
callback=nop;
191+
stream.removeListener('error',nop);
192+
};
193+
}
194+
}elseif(options.signal?.aborted){
195+
immediateResult=newAbortError(undefined,{cause: options.signal.reason});
196+
}
197+
if(immediateResult!==undefined&&options[kEosNodeSynchronousCallback]){
198+
ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]);
199+
returncleanup;
200+
}
201+
202+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
203+
// is a bottleneck here.
204+
callback=bindAsyncResource(callback,'STREAM_END_OF_STREAM');
205+
206+
if(immediateResult!==undefined){
207+
process.nextTick(()=>ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]));
208+
returncleanup;
209+
}
210+
211+
callback=once(callback);
212+
213+
constonlegacyfinish=()=>{
214+
if(!stream.writable){
215+
onfinish();
216+
}
217+
};
218+
111219
constonfinish=()=>{
112220
writableFinished=true;
113221
// Stream should not be destroyed here. If it is that
@@ -126,7 +234,6 @@ function eos(stream, options, callback) {
126234
}
127235
};
128236

129-
letreadableFinished=isReadableFinished(stream,false);
130237
constonend=()=>{
131238
readableFinished=true;
132239
// Stream should not be destroyed here. If it is that
@@ -149,41 +256,13 @@ function eos(stream, options, callback) {
149256
callback.call(stream,err);
150257
};
151258

152-
letclosed=isClosed(stream);
153-
154259
constonclose=()=>{
155-
closed=true;
156-
157-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
158-
159-
if(errored&&typeoferrored!=='boolean'){
160-
returncallback.call(stream,errored);
161-
}
162-
163-
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
164-
if(!isReadableFinished(stream,false))
165-
returncallback.call(stream,
166-
newERR_STREAM_PREMATURE_CLOSE());
167-
}
168-
if(writable&&!writableFinished){
169-
if(!isWritableFinished(stream,false))
170-
returncallback.call(stream,
171-
newERR_STREAM_PREMATURE_CLOSE());
172-
}
173-
174-
callback.call(stream);
175-
};
176-
177-
constonclosed=()=>{
178-
closed=true;
179-
180-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
181-
182-
if(errored&&typeoferrored!=='boolean'){
183-
returncallback.call(stream,errored);
260+
consterror=getEosOnCloseError(stream,readable,readableFinished,writable,writableFinished);
261+
if(error===null){
262+
callback.call(stream);
263+
}else{
264+
callback.call(stream,error);
184265
}
185-
186-
callback.call(stream);
187266
};
188267

189268
constonrequest=()=>{
@@ -217,30 +296,7 @@ function eos(stream, options, callback) {
217296
}
218297
stream.on('close',onclose);
219298

220-
if(closed){
221-
process.nextTick(onclose);
222-
}elseif(wState?.errorEmitted||rState?.errorEmitted){
223-
if(!willEmitClose){
224-
process.nextTick(onclosed);
225-
}
226-
}elseif(
227-
!readable&&
228-
(!willEmitClose||isReadable(stream))&&
229-
(writableFinished||isWritable(stream)===false)&&
230-
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
231-
){
232-
process.nextTick(onclosed);
233-
}elseif(
234-
!writable&&
235-
(!willEmitClose||isWritable(stream))&&
236-
(readableFinished||isReadable(stream)===false)
237-
){
238-
process.nextTick(onclosed);
239-
}elseif((rState&&stream.req&&stream.aborted)){
240-
process.nextTick(onclosed);
241-
}
242-
243-
constcleanup=()=>{
299+
cleanup=()=>{
244300
callback=nop;
245301
stream.removeListener('aborted',onclose);
246302
stream.removeListener('complete',onfinish);
@@ -255,7 +311,7 @@ function eos(stream, options, callback) {
255311
stream.removeListener('close',onclose);
256312
};
257313

258-
if(options.signal&&!closed){
314+
if(options.signal){
259315
constabort=()=>{
260316
// Keep it because cleanup removes it.
261317
constendCallback=callback;
@@ -264,23 +320,23 @@ function eos(stream, options, callback) {
264320
stream,
265321
newAbortError(undefined,{cause: options.signal.reason}));
266322
};
267-
if(options.signal.aborted){
268-
process.nextTick(abort);
269-
}else{
270-
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
271-
constdisposable=addAbortListener(options.signal,abort);
272-
constoriginalCallback=callback;
273-
callback=once((...args)=>{
274-
disposable[SymbolDispose]();
275-
originalCallback.apply(stream,args);
276-
});
277-
}
323+
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
324+
constdisposable=addAbortListener(options.signal,abort);
325+
constoriginalCallback=callback;
326+
callback=once((...args)=>{
327+
disposable[SymbolDispose]();
328+
ReflectApply(originalCallback,stream,args);
329+
});
278330
}
279331

280332
returncleanup;
281333
}
282334

283335
functioneosWeb(stream,options,callback){
336+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
337+
// is a bottleneck here.
338+
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
339+
284340
letisAborted=false;
285341
letabort=nop;
286342
if(options.signal){
@@ -339,4 +395,5 @@ function finished(stream, opts) {
339395
module.exports={
340396
eos,
341397
finished,
398+
kEosNodeSynchronousCallback,
342399
};

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 5658c63

Browse files
ikeyanaduh95
authored andcommitted
stream: align Readable.toWeb termination with eos
PR-URL: #62394 Backport-PR-URL: #64675 Reviewed-By: Mattias Buelens <mattias@buelens.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cc425bc commit 5658c63

5 files changed

Lines changed: 416 additions & 164 deletions

File tree

β€Žlib/internal/streams/end-of-stream.jsβ€Ž

Lines changed: 141 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
const{
77
Promise,
88
PromisePrototypeThen,
9+
ReflectApply,
10+
Symbol,
911
SymbolDispose,
1012
}=primordials;
1113

@@ -62,6 +64,50 @@ function bindAsyncResource(fn, type) {
6264
};
6365
}
6466

67+
/**
68+
* Returns the current stream error tracked by eos(), if any.
69+
* @param {import('stream').Stream} stream
70+
* @returns {Error | null}
71+
*/
72+
functiongetEosErrored(stream){
73+
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
74+
returntypeoferrored!=='boolean'&&errored||null;
75+
}
76+
77+
/**
78+
* Returns the error eos() would report from an immediate close, including
79+
* premature close detection for unfinished readable or writable sides.
80+
* @param {import('stream').Stream} stream
81+
* @param {boolean} readable
82+
* @param {boolean | null} readableFinished
83+
* @param {boolean} writable
84+
* @param {boolean | null} writableFinished
85+
* @returns {Error | null}
86+
*/
87+
functiongetEosOnCloseError(stream,readable,readableFinished,writable,writableFinished){
88+
consterrored=getEosErrored(stream);
89+
if(errored){
90+
returnerrored;
91+
}
92+
93+
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
94+
if(!isReadableFinished(stream,false)){
95+
returnnewERR_STREAM_PREMATURE_CLOSE();
96+
}
97+
}
98+
if(writable&&!writableFinished){
99+
if(!isWritableFinished(stream,false)){
100+
returnnewERR_STREAM_PREMATURE_CLOSE();
101+
}
102+
}
103+
104+
returnnull;
105+
}
106+
107+
// Internal only: if eos() can settle immediately, invoke the callback before
108+
// returning cleanup. Callers must tolerate cleanup yet to be assigned.
109+
constkEosNodeSynchronousCallback=Symbol('kEosNodeSynchronousCallback');
110+
65111
functioneos(stream,options,callback){
66112
if(arguments.length===2){
67113
callback=options;
@@ -74,10 +120,6 @@ function eos(stream, options, callback) {
74120
validateFunction(callback,'callback');
75121
validateAbortSignal(options.signal,'options.signal');
76122

77-
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
78-
// is a bottleneck here.
79-
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
80-
81123
if(isReadableStream(stream)||isWritableStream(stream)){
82124
returneosWeb(stream,options,callback);
83125
}
@@ -89,15 +131,6 @@ function eos(stream, options, callback) {
89131
constreadable=options.readable??isReadableNodeStream(stream);
90132
constwritable=options.writable??isWritableNodeStream(stream);
91133

92-
constwState=stream._writableState;
93-
constrState=stream._readableState;
94-
95-
constonlegacyfinish=()=>{
96-
if(!stream.writable){
97-
onfinish();
98-
}
99-
};
100-
101134
// TODO (ronag): Improve soft detection to include core modules and
102135
// common ecosystem modules that do properly emit 'close' but fail
103136
// this generic check.
@@ -106,8 +139,83 @@ function eos(stream, options, callback) {
106139
isReadableNodeStream(stream)===readable&&
107140
isWritableNodeStream(stream)===writable
108141
);
109-
110142
letwritableFinished=isWritableFinished(stream,false);
143+
letreadableFinished=isReadableFinished(stream,false);
144+
145+
constwState=stream._writableState;
146+
constrState=stream._readableState;
147+
148+
/**
149+
* @type {Error | null | undefined}
150+
* undefined: to be determined
151+
* null: no error
152+
* Error: an error occurred
153+
*/
154+
letimmediateResult;
155+
if(isClosed(stream)){
156+
immediateResult=getEosOnCloseError(
157+
stream,
158+
readable,
159+
readableFinished,
160+
writable,
161+
writableFinished,
162+
);
163+
}elseif(wState?.errorEmitted||rState?.errorEmitted){
164+
if(!willEmitClose){
165+
immediateResult=getEosErrored(stream);
166+
}
167+
}elseif(
168+
!readable&&
169+
(!willEmitClose||isReadable(stream))&&
170+
(writableFinished||isWritable(stream)===false)&&
171+
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
172+
){
173+
immediateResult=getEosErrored(stream);
174+
}elseif(
175+
!writable&&
176+
(!willEmitClose||isWritable(stream))&&
177+
(readableFinished||isReadable(stream)===false)
178+
){
179+
immediateResult=getEosErrored(stream);
180+
}elseif((rState&&stream.req&&stream.aborted)){
181+
immediateResult=getEosErrored(stream);
182+
}
183+
letcleanup=()=>{
184+
callback=nop;
185+
};
186+
if(immediateResult!==undefined){
187+
if(options.error!==false){
188+
stream.on('error',nop);
189+
cleanup=()=>{
190+
callback=nop;
191+
stream.removeListener('error',nop);
192+
};
193+
}
194+
}elseif(options.signal?.aborted){
195+
immediateResult=newAbortError(undefined,{cause: options.signal.reason});
196+
}
197+
if(immediateResult!==undefined&&options[kEosNodeSynchronousCallback]){
198+
ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]);
199+
returncleanup;
200+
}
201+
202+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
203+
// is a bottleneck here.
204+
callback=bindAsyncResource(callback,'STREAM_END_OF_STREAM');
205+
206+
if(immediateResult!==undefined){
207+
process.nextTick(()=>ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]));
208+
returncleanup;
209+
}
210+
211+
callback=once(callback);
212+
213+
constonlegacyfinish=()=>{
214+
if(!stream.writable){
215+
onfinish();
216+
}
217+
};
218+
111219
constonfinish=()=>{
112220
writableFinished=true;
113221
// Stream should not be destroyed here. If it is that
@@ -126,7 +234,6 @@ function eos(stream, options, callback) {
126234
}
127235
};
128236

129-
letreadableFinished=isReadableFinished(stream,false);
130237
constonend=()=>{
131238
readableFinished=true;
132239
// Stream should not be destroyed here. If it is that
@@ -149,41 +256,13 @@ function eos(stream, options, callback) {
149256
callback.call(stream,err);
150257
};
151258

152-
letclosed=isClosed(stream);
153-
154259
constonclose=()=>{
155-
closed=true;
156-
157-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
158-
159-
if(errored&&typeoferrored!=='boolean'){
160-
returncallback.call(stream,errored);
161-
}
162-
163-
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
164-
if(!isReadableFinished(stream,false))
165-
returncallback.call(stream,
166-
newERR_STREAM_PREMATURE_CLOSE());
167-
}
168-
if(writable&&!writableFinished){
169-
if(!isWritableFinished(stream,false))
170-
returncallback.call(stream,
171-
newERR_STREAM_PREMATURE_CLOSE());
172-
}
173-
174-
callback.call(stream);
175-
};
176-
177-
constonclosed=()=>{
178-
closed=true;
179-
180-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
181-
182-
if(errored&&typeoferrored!=='boolean'){
183-
returncallback.call(stream,errored);
260+
consterror=getEosOnCloseError(stream,readable,readableFinished,writable,writableFinished);
261+
if(error===null){
262+
callback.call(stream);
263+
}else{
264+
callback.call(stream,error);
184265
}
185-
186-
callback.call(stream);
187266
};
188267

189268
constonrequest=()=>{
@@ -217,30 +296,7 @@ function eos(stream, options, callback) {
217296
}
218297
stream.on('close',onclose);
219298

220-
if(closed){
221-
process.nextTick(onclose);
222-
}elseif(wState?.errorEmitted||rState?.errorEmitted){
223-
if(!willEmitClose){
224-
process.nextTick(onclosed);
225-
}
226-
}elseif(
227-
!readable&&
228-
(!willEmitClose||isReadable(stream))&&
229-
(writableFinished||isWritable(stream)===false)&&
230-
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
231-
){
232-
process.nextTick(onclosed);
233-
}elseif(
234-
!writable&&
235-
(!willEmitClose||isWritable(stream))&&
236-
(readableFinished||isReadable(stream)===false)
237-
){
238-
process.nextTick(onclosed);
239-
}elseif((rState&&stream.req&&stream.aborted)){
240-
process.nextTick(onclosed);
241-
}
242-
243-
constcleanup=()=>{
299+
cleanup=()=>{
244300
callback=nop;
245301
stream.removeListener('aborted',onclose);
246302
stream.removeListener('complete',onfinish);
@@ -255,7 +311,7 @@ function eos(stream, options, callback) {
255311
stream.removeListener('close',onclose);
256312
};
257313

258-
if(options.signal&&!closed){
314+
if(options.signal){
259315
constabort=()=>{
260316
// Keep it because cleanup removes it.
261317
constendCallback=callback;
@@ -264,23 +320,23 @@ function eos(stream, options, callback) {
264320
stream,
265321
newAbortError(undefined,{cause: options.signal.reason}));
266322
};
267-
if(options.signal.aborted){
268-
process.nextTick(abort);
269-
}else{
270-
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
271-
constdisposable=addAbortListener(options.signal,abort);
272-
constoriginalCallback=callback;
273-
callback=once((...args)=>{
274-
disposable[SymbolDispose]();
275-
originalCallback.apply(stream,args);
276-
});
277-
}
323+
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
324+
constdisposable=addAbortListener(options.signal,abort);
325+
constoriginalCallback=callback;
326+
callback=once((...args)=>{
327+
disposable[SymbolDispose]();
328+
ReflectApply(originalCallback,stream,args);
329+
});
278330
}
279331

280332
returncleanup;
281333
}
282334

283335
functioneosWeb(stream,options,callback){
336+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
337+
// is a bottleneck here.
338+
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
339+
284340
letisAborted=false;
285341
letabort=nop;
286342
if(options.signal){
@@ -339,4 +395,5 @@ function finished(stream, opts) {
339395
module.exports={
340396
eos,
341397
finished,
398+
kEosNodeSynchronousCallback,
342399
};

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 5658c63

Browse files
ikeyanaduh95
authored andcommitted
stream: align Readable.toWeb termination with eos
PR-URL: #62394 Backport-PR-URL: #64675 Reviewed-By: Mattias Buelens <mattias@buelens.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cc425bc commit 5658c63

5 files changed

Lines changed: 416 additions & 164 deletions

File tree

β€Žlib/internal/streams/end-of-stream.jsβ€Ž

Lines changed: 141 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
const{
77
Promise,
88
PromisePrototypeThen,
9+
ReflectApply,
10+
Symbol,
911
SymbolDispose,
1012
}=primordials;
1113

@@ -62,6 +64,50 @@ function bindAsyncResource(fn, type) {
6264
};
6365
}
6466

67+
/**
68+
* Returns the current stream error tracked by eos(), if any.
69+
* @param {import('stream').Stream} stream
70+
* @returns {Error | null}
71+
*/
72+
functiongetEosErrored(stream){
73+
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
74+
returntypeoferrored!=='boolean'&&errored||null;
75+
}
76+
77+
/**
78+
* Returns the error eos() would report from an immediate close, including
79+
* premature close detection for unfinished readable or writable sides.
80+
* @param {import('stream').Stream} stream
81+
* @param {boolean} readable
82+
* @param {boolean | null} readableFinished
83+
* @param {boolean} writable
84+
* @param {boolean | null} writableFinished
85+
* @returns {Error | null}
86+
*/
87+
functiongetEosOnCloseError(stream,readable,readableFinished,writable,writableFinished){
88+
consterrored=getEosErrored(stream);
89+
if(errored){
90+
returnerrored;
91+
}
92+
93+
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
94+
if(!isReadableFinished(stream,false)){
95+
returnnewERR_STREAM_PREMATURE_CLOSE();
96+
}
97+
}
98+
if(writable&&!writableFinished){
99+
if(!isWritableFinished(stream,false)){
100+
returnnewERR_STREAM_PREMATURE_CLOSE();
101+
}
102+
}
103+
104+
returnnull;
105+
}
106+
107+
// Internal only: if eos() can settle immediately, invoke the callback before
108+
// returning cleanup. Callers must tolerate cleanup yet to be assigned.
109+
constkEosNodeSynchronousCallback=Symbol('kEosNodeSynchronousCallback');
110+
65111
functioneos(stream,options,callback){
66112
if(arguments.length===2){
67113
callback=options;
@@ -74,10 +120,6 @@ function eos(stream, options, callback) {
74120
validateFunction(callback,'callback');
75121
validateAbortSignal(options.signal,'options.signal');
76122

77-
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
78-
// is a bottleneck here.
79-
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
80-
81123
if(isReadableStream(stream)||isWritableStream(stream)){
82124
returneosWeb(stream,options,callback);
83125
}
@@ -89,15 +131,6 @@ function eos(stream, options, callback) {
89131
constreadable=options.readable??isReadableNodeStream(stream);
90132
constwritable=options.writable??isWritableNodeStream(stream);
91133

92-
constwState=stream._writableState;
93-
constrState=stream._readableState;
94-
95-
constonlegacyfinish=()=>{
96-
if(!stream.writable){
97-
onfinish();
98-
}
99-
};
100-
101134
// TODO (ronag): Improve soft detection to include core modules and
102135
// common ecosystem modules that do properly emit 'close' but fail
103136
// this generic check.
@@ -106,8 +139,83 @@ function eos(stream, options, callback) {
106139
isReadableNodeStream(stream)===readable&&
107140
isWritableNodeStream(stream)===writable
108141
);
109-
110142
letwritableFinished=isWritableFinished(stream,false);
143+
letreadableFinished=isReadableFinished(stream,false);
144+
145+
constwState=stream._writableState;
146+
constrState=stream._readableState;
147+
148+
/**
149+
* @type {Error | null | undefined}
150+
* undefined: to be determined
151+
* null: no error
152+
* Error: an error occurred
153+
*/
154+
letimmediateResult;
155+
if(isClosed(stream)){
156+
immediateResult=getEosOnCloseError(
157+
stream,
158+
readable,
159+
readableFinished,
160+
writable,
161+
writableFinished,
162+
);
163+
}elseif(wState?.errorEmitted||rState?.errorEmitted){
164+
if(!willEmitClose){
165+
immediateResult=getEosErrored(stream);
166+
}
167+
}elseif(
168+
!readable&&
169+
(!willEmitClose||isReadable(stream))&&
170+
(writableFinished||isWritable(stream)===false)&&
171+
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
172+
){
173+
immediateResult=getEosErrored(stream);
174+
}elseif(
175+
!writable&&
176+
(!willEmitClose||isWritable(stream))&&
177+
(readableFinished||isReadable(stream)===false)
178+
){
179+
immediateResult=getEosErrored(stream);
180+
}elseif((rState&&stream.req&&stream.aborted)){
181+
immediateResult=getEosErrored(stream);
182+
}
183+
letcleanup=()=>{
184+
callback=nop;
185+
};
186+
if(immediateResult!==undefined){
187+
if(options.error!==false){
188+
stream.on('error',nop);
189+
cleanup=()=>{
190+
callback=nop;
191+
stream.removeListener('error',nop);
192+
};
193+
}
194+
}elseif(options.signal?.aborted){
195+
immediateResult=newAbortError(undefined,{cause: options.signal.reason});
196+
}
197+
if(immediateResult!==undefined&&options[kEosNodeSynchronousCallback]){
198+
ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]);
199+
returncleanup;
200+
}
201+
202+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
203+
// is a bottleneck here.
204+
callback=bindAsyncResource(callback,'STREAM_END_OF_STREAM');
205+
206+
if(immediateResult!==undefined){
207+
process.nextTick(()=>ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]));
208+
returncleanup;
209+
}
210+
211+
callback=once(callback);
212+
213+
constonlegacyfinish=()=>{
214+
if(!stream.writable){
215+
onfinish();
216+
}
217+
};
218+
111219
constonfinish=()=>{
112220
writableFinished=true;
113221
// Stream should not be destroyed here. If it is that
@@ -126,7 +234,6 @@ function eos(stream, options, callback) {
126234
}
127235
};
128236

129-
letreadableFinished=isReadableFinished(stream,false);
130237
constonend=()=>{
131238
readableFinished=true;
132239
// Stream should not be destroyed here. If it is that
@@ -149,41 +256,13 @@ function eos(stream, options, callback) {
149256
callback.call(stream,err);
150257
};
151258

152-
letclosed=isClosed(stream);
153-
154259
constonclose=()=>{
155-
closed=true;
156-
157-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
158-
159-
if(errored&&typeoferrored!=='boolean'){
160-
returncallback.call(stream,errored);
161-
}
162-
163-
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
164-
if(!isReadableFinished(stream,false))
165-
returncallback.call(stream,
166-
newERR_STREAM_PREMATURE_CLOSE());
167-
}
168-
if(writable&&!writableFinished){
169-
if(!isWritableFinished(stream,false))
170-
returncallback.call(stream,
171-
newERR_STREAM_PREMATURE_CLOSE());
172-
}
173-
174-
callback.call(stream);
175-
};
176-
177-
constonclosed=()=>{
178-
closed=true;
179-
180-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
181-
182-
if(errored&&typeoferrored!=='boolean'){
183-
returncallback.call(stream,errored);
260+
consterror=getEosOnCloseError(stream,readable,readableFinished,writable,writableFinished);
261+
if(error===null){
262+
callback.call(stream);
263+
}else{
264+
callback.call(stream,error);
184265
}
185-
186-
callback.call(stream);
187266
};
188267

189268
constonrequest=()=>{
@@ -217,30 +296,7 @@ function eos(stream, options, callback) {
217296
}
218297
stream.on('close',onclose);
219298

220-
if(closed){
221-
process.nextTick(onclose);
222-
}elseif(wState?.errorEmitted||rState?.errorEmitted){
223-
if(!willEmitClose){
224-
process.nextTick(onclosed);
225-
}
226-
}elseif(
227-
!readable&&
228-
(!willEmitClose||isReadable(stream))&&
229-
(writableFinished||isWritable(stream)===false)&&
230-
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
231-
){
232-
process.nextTick(onclosed);
233-
}elseif(
234-
!writable&&
235-
(!willEmitClose||isWritable(stream))&&
236-
(readableFinished||isReadable(stream)===false)
237-
){
238-
process.nextTick(onclosed);
239-
}elseif((rState&&stream.req&&stream.aborted)){
240-
process.nextTick(onclosed);
241-
}
242-
243-
constcleanup=()=>{
299+
cleanup=()=>{
244300
callback=nop;
245301
stream.removeListener('aborted',onclose);
246302
stream.removeListener('complete',onfinish);
@@ -255,7 +311,7 @@ function eos(stream, options, callback) {
255311
stream.removeListener('close',onclose);
256312
};
257313

258-
if(options.signal&&!closed){
314+
if(options.signal){
259315
constabort=()=>{
260316
// Keep it because cleanup removes it.
261317
constendCallback=callback;
@@ -264,23 +320,23 @@ function eos(stream, options, callback) {
264320
stream,
265321
newAbortError(undefined,{cause: options.signal.reason}));
266322
};
267-
if(options.signal.aborted){
268-
process.nextTick(abort);
269-
}else{
270-
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
271-
constdisposable=addAbortListener(options.signal,abort);
272-
constoriginalCallback=callback;
273-
callback=once((...args)=>{
274-
disposable[SymbolDispose]();
275-
originalCallback.apply(stream,args);
276-
});
277-
}
323+
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
324+
constdisposable=addAbortListener(options.signal,abort);
325+
constoriginalCallback=callback;
326+
callback=once((...args)=>{
327+
disposable[SymbolDispose]();
328+
ReflectApply(originalCallback,stream,args);
329+
});
278330
}
279331

280332
returncleanup;
281333
}
282334

283335
functioneosWeb(stream,options,callback){
336+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
337+
// is a bottleneck here.
338+
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
339+
284340
letisAborted=false;
285341
letabort=nop;
286342
if(options.signal){
@@ -339,4 +395,5 @@ function finished(stream, opts) {
339395
module.exports={
340396
eos,
341397
finished,
398+
kEosNodeSynchronousCallback,
342399
};

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 5658c63

Browse files
ikeyanaduh95
authored andcommitted
stream: align Readable.toWeb termination with eos
PR-URL: #62394 Backport-PR-URL: #64675 Reviewed-By: Mattias Buelens <mattias@buelens.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cc425bc commit 5658c63

5 files changed

Lines changed: 416 additions & 164 deletions

File tree

β€Žlib/internal/streams/end-of-stream.jsβ€Ž

Lines changed: 141 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
const{
77
Promise,
88
PromisePrototypeThen,
9+
ReflectApply,
10+
Symbol,
911
SymbolDispose,
1012
}=primordials;
1113

@@ -62,6 +64,50 @@ function bindAsyncResource(fn, type) {
6264
};
6365
}
6466

67+
/**
68+
* Returns the current stream error tracked by eos(), if any.
69+
* @param {import('stream').Stream} stream
70+
* @returns {Error | null}
71+
*/
72+
functiongetEosErrored(stream){
73+
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
74+
returntypeoferrored!=='boolean'&&errored||null;
75+
}
76+
77+
/**
78+
* Returns the error eos() would report from an immediate close, including
79+
* premature close detection for unfinished readable or writable sides.
80+
* @param {import('stream').Stream} stream
81+
* @param {boolean} readable
82+
* @param {boolean | null} readableFinished
83+
* @param {boolean} writable
84+
* @param {boolean | null} writableFinished
85+
* @returns {Error | null}
86+
*/
87+
functiongetEosOnCloseError(stream,readable,readableFinished,writable,writableFinished){
88+
consterrored=getEosErrored(stream);
89+
if(errored){
90+
returnerrored;
91+
}
92+
93+
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
94+
if(!isReadableFinished(stream,false)){
95+
returnnewERR_STREAM_PREMATURE_CLOSE();
96+
}
97+
}
98+
if(writable&&!writableFinished){
99+
if(!isWritableFinished(stream,false)){
100+
returnnewERR_STREAM_PREMATURE_CLOSE();
101+
}
102+
}
103+
104+
returnnull;
105+
}
106+
107+
// Internal only: if eos() can settle immediately, invoke the callback before
108+
// returning cleanup. Callers must tolerate cleanup yet to be assigned.
109+
constkEosNodeSynchronousCallback=Symbol('kEosNodeSynchronousCallback');
110+
65111
functioneos(stream,options,callback){
66112
if(arguments.length===2){
67113
callback=options;
@@ -74,10 +120,6 @@ function eos(stream, options, callback) {
74120
validateFunction(callback,'callback');
75121
validateAbortSignal(options.signal,'options.signal');
76122

77-
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
78-
// is a bottleneck here.
79-
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
80-
81123
if(isReadableStream(stream)||isWritableStream(stream)){
82124
returneosWeb(stream,options,callback);
83125
}
@@ -89,15 +131,6 @@ function eos(stream, options, callback) {
89131
constreadable=options.readable??isReadableNodeStream(stream);
90132
constwritable=options.writable??isWritableNodeStream(stream);
91133

92-
constwState=stream._writableState;
93-
constrState=stream._readableState;
94-
95-
constonlegacyfinish=()=>{
96-
if(!stream.writable){
97-
onfinish();
98-
}
99-
};
100-
101134
// TODO (ronag): Improve soft detection to include core modules and
102135
// common ecosystem modules that do properly emit 'close' but fail
103136
// this generic check.
@@ -106,8 +139,83 @@ function eos(stream, options, callback) {
106139
isReadableNodeStream(stream)===readable&&
107140
isWritableNodeStream(stream)===writable
108141
);
109-
110142
letwritableFinished=isWritableFinished(stream,false);
143+
letreadableFinished=isReadableFinished(stream,false);
144+
145+
constwState=stream._writableState;
146+
constrState=stream._readableState;
147+
148+
/**
149+
* @type {Error | null | undefined}
150+
* undefined: to be determined
151+
* null: no error
152+
* Error: an error occurred
153+
*/
154+
letimmediateResult;
155+
if(isClosed(stream)){
156+
immediateResult=getEosOnCloseError(
157+
stream,
158+
readable,
159+
readableFinished,
160+
writable,
161+
writableFinished,
162+
);
163+
}elseif(wState?.errorEmitted||rState?.errorEmitted){
164+
if(!willEmitClose){
165+
immediateResult=getEosErrored(stream);
166+
}
167+
}elseif(
168+
!readable&&
169+
(!willEmitClose||isReadable(stream))&&
170+
(writableFinished||isWritable(stream)===false)&&
171+
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
172+
){
173+
immediateResult=getEosErrored(stream);
174+
}elseif(
175+
!writable&&
176+
(!willEmitClose||isWritable(stream))&&
177+
(readableFinished||isReadable(stream)===false)
178+
){
179+
immediateResult=getEosErrored(stream);
180+
}elseif((rState&&stream.req&&stream.aborted)){
181+
immediateResult=getEosErrored(stream);
182+
}
183+
letcleanup=()=>{
184+
callback=nop;
185+
};
186+
if(immediateResult!==undefined){
187+
if(options.error!==false){
188+
stream.on('error',nop);
189+
cleanup=()=>{
190+
callback=nop;
191+
stream.removeListener('error',nop);
192+
};
193+
}
194+
}elseif(options.signal?.aborted){
195+
immediateResult=newAbortError(undefined,{cause: options.signal.reason});
196+
}
197+
if(immediateResult!==undefined&&options[kEosNodeSynchronousCallback]){
198+
ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]);
199+
returncleanup;
200+
}
201+
202+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
203+
// is a bottleneck here.
204+
callback=bindAsyncResource(callback,'STREAM_END_OF_STREAM');
205+
206+
if(immediateResult!==undefined){
207+
process.nextTick(()=>ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]));
208+
returncleanup;
209+
}
210+
211+
callback=once(callback);
212+
213+
constonlegacyfinish=()=>{
214+
if(!stream.writable){
215+
onfinish();
216+
}
217+
};
218+
111219
constonfinish=()=>{
112220
writableFinished=true;
113221
// Stream should not be destroyed here. If it is that
@@ -126,7 +234,6 @@ function eos(stream, options, callback) {
126234
}
127235
};
128236

129-
letreadableFinished=isReadableFinished(stream,false);
130237
constonend=()=>{
131238
readableFinished=true;
132239
// Stream should not be destroyed here. If it is that
@@ -149,41 +256,13 @@ function eos(stream, options, callback) {
149256
callback.call(stream,err);
150257
};
151258

152-
letclosed=isClosed(stream);
153-
154259
constonclose=()=>{
155-
closed=true;
156-
157-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
158-
159-
if(errored&&typeoferrored!=='boolean'){
160-
returncallback.call(stream,errored);
161-
}
162-
163-
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
164-
if(!isReadableFinished(stream,false))
165-
returncallback.call(stream,
166-
newERR_STREAM_PREMATURE_CLOSE());
167-
}
168-
if(writable&&!writableFinished){
169-
if(!isWritableFinished(stream,false))
170-
returncallback.call(stream,
171-
newERR_STREAM_PREMATURE_CLOSE());
172-
}
173-
174-
callback.call(stream);
175-
};
176-
177-
constonclosed=()=>{
178-
closed=true;
179-
180-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
181-
182-
if(errored&&typeoferrored!=='boolean'){
183-
returncallback.call(stream,errored);
260+
consterror=getEosOnCloseError(stream,readable,readableFinished,writable,writableFinished);
261+
if(error===null){
262+
callback.call(stream);
263+
}else{
264+
callback.call(stream,error);
184265
}
185-
186-
callback.call(stream);
187266
};
188267

189268
constonrequest=()=>{
@@ -217,30 +296,7 @@ function eos(stream, options, callback) {
217296
}
218297
stream.on('close',onclose);
219298

220-
if(closed){
221-
process.nextTick(onclose);
222-
}elseif(wState?.errorEmitted||rState?.errorEmitted){
223-
if(!willEmitClose){
224-
process.nextTick(onclosed);
225-
}
226-
}elseif(
227-
!readable&&
228-
(!willEmitClose||isReadable(stream))&&
229-
(writableFinished||isWritable(stream)===false)&&
230-
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
231-
){
232-
process.nextTick(onclosed);
233-
}elseif(
234-
!writable&&
235-
(!willEmitClose||isWritable(stream))&&
236-
(readableFinished||isReadable(stream)===false)
237-
){
238-
process.nextTick(onclosed);
239-
}elseif((rState&&stream.req&&stream.aborted)){
240-
process.nextTick(onclosed);
241-
}
242-
243-
constcleanup=()=>{
299+
cleanup=()=>{
244300
callback=nop;
245301
stream.removeListener('aborted',onclose);
246302
stream.removeListener('complete',onfinish);
@@ -255,7 +311,7 @@ function eos(stream, options, callback) {
255311
stream.removeListener('close',onclose);
256312
};
257313

258-
if(options.signal&&!closed){
314+
if(options.signal){
259315
constabort=()=>{
260316
// Keep it because cleanup removes it.
261317
constendCallback=callback;
@@ -264,23 +320,23 @@ function eos(stream, options, callback) {
264320
stream,
265321
newAbortError(undefined,{cause: options.signal.reason}));
266322
};
267-
if(options.signal.aborted){
268-
process.nextTick(abort);
269-
}else{
270-
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
271-
constdisposable=addAbortListener(options.signal,abort);
272-
constoriginalCallback=callback;
273-
callback=once((...args)=>{
274-
disposable[SymbolDispose]();
275-
originalCallback.apply(stream,args);
276-
});
277-
}
323+
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
324+
constdisposable=addAbortListener(options.signal,abort);
325+
constoriginalCallback=callback;
326+
callback=once((...args)=>{
327+
disposable[SymbolDispose]();
328+
ReflectApply(originalCallback,stream,args);
329+
});
278330
}
279331

280332
returncleanup;
281333
}
282334

283335
functioneosWeb(stream,options,callback){
336+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
337+
// is a bottleneck here.
338+
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
339+
284340
letisAborted=false;
285341
letabort=nop;
286342
if(options.signal){
@@ -339,4 +395,5 @@ function finished(stream, opts) {
339395
module.exports={
340396
eos,
341397
finished,
398+
kEosNodeSynchronousCallback,
342399
};

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 5658c63

Browse files
ikeyanaduh95
authored andcommitted
stream: align Readable.toWeb termination with eos
PR-URL: #62394 Backport-PR-URL: #64675 Reviewed-By: Mattias Buelens <mattias@buelens.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cc425bc commit 5658c63

5 files changed

Lines changed: 416 additions & 164 deletions

File tree

β€Žlib/internal/streams/end-of-stream.jsβ€Ž

Lines changed: 141 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
const{
77
Promise,
88
PromisePrototypeThen,
9+
ReflectApply,
10+
Symbol,
911
SymbolDispose,
1012
}=primordials;
1113

@@ -62,6 +64,50 @@ function bindAsyncResource(fn, type) {
6264
};
6365
}
6466

67+
/**
68+
* Returns the current stream error tracked by eos(), if any.
69+
* @param {import('stream').Stream} stream
70+
* @returns {Error | null}
71+
*/
72+
functiongetEosErrored(stream){
73+
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
74+
returntypeoferrored!=='boolean'&&errored||null;
75+
}
76+
77+
/**
78+
* Returns the error eos() would report from an immediate close, including
79+
* premature close detection for unfinished readable or writable sides.
80+
* @param {import('stream').Stream} stream
81+
* @param {boolean} readable
82+
* @param {boolean | null} readableFinished
83+
* @param {boolean} writable
84+
* @param {boolean | null} writableFinished
85+
* @returns {Error | null}
86+
*/
87+
functiongetEosOnCloseError(stream,readable,readableFinished,writable,writableFinished){
88+
consterrored=getEosErrored(stream);
89+
if(errored){
90+
returnerrored;
91+
}
92+
93+
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
94+
if(!isReadableFinished(stream,false)){
95+
returnnewERR_STREAM_PREMATURE_CLOSE();
96+
}
97+
}
98+
if(writable&&!writableFinished){
99+
if(!isWritableFinished(stream,false)){
100+
returnnewERR_STREAM_PREMATURE_CLOSE();
101+
}
102+
}
103+
104+
returnnull;
105+
}
106+
107+
// Internal only: if eos() can settle immediately, invoke the callback before
108+
// returning cleanup. Callers must tolerate cleanup yet to be assigned.
109+
constkEosNodeSynchronousCallback=Symbol('kEosNodeSynchronousCallback');
110+
65111
functioneos(stream,options,callback){
66112
if(arguments.length===2){
67113
callback=options;
@@ -74,10 +120,6 @@ function eos(stream, options, callback) {
74120
validateFunction(callback,'callback');
75121
validateAbortSignal(options.signal,'options.signal');
76122

77-
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
78-
// is a bottleneck here.
79-
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
80-
81123
if(isReadableStream(stream)||isWritableStream(stream)){
82124
returneosWeb(stream,options,callback);
83125
}
@@ -89,15 +131,6 @@ function eos(stream, options, callback) {
89131
constreadable=options.readable??isReadableNodeStream(stream);
90132
constwritable=options.writable??isWritableNodeStream(stream);
91133

92-
constwState=stream._writableState;
93-
constrState=stream._readableState;
94-
95-
constonlegacyfinish=()=>{
96-
if(!stream.writable){
97-
onfinish();
98-
}
99-
};
100-
101134
// TODO (ronag): Improve soft detection to include core modules and
102135
// common ecosystem modules that do properly emit 'close' but fail
103136
// this generic check.
@@ -106,8 +139,83 @@ function eos(stream, options, callback) {
106139
isReadableNodeStream(stream)===readable&&
107140
isWritableNodeStream(stream)===writable
108141
);
109-
110142
letwritableFinished=isWritableFinished(stream,false);
143+
letreadableFinished=isReadableFinished(stream,false);
144+
145+
constwState=stream._writableState;
146+
constrState=stream._readableState;
147+
148+
/**
149+
* @type {Error | null | undefined}
150+
* undefined: to be determined
151+
* null: no error
152+
* Error: an error occurred
153+
*/
154+
letimmediateResult;
155+
if(isClosed(stream)){
156+
immediateResult=getEosOnCloseError(
157+
stream,
158+
readable,
159+
readableFinished,
160+
writable,
161+
writableFinished,
162+
);
163+
}elseif(wState?.errorEmitted||rState?.errorEmitted){
164+
if(!willEmitClose){
165+
immediateResult=getEosErrored(stream);
166+
}
167+
}elseif(
168+
!readable&&
169+
(!willEmitClose||isReadable(stream))&&
170+
(writableFinished||isWritable(stream)===false)&&
171+
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
172+
){
173+
immediateResult=getEosErrored(stream);
174+
}elseif(
175+
!writable&&
176+
(!willEmitClose||isWritable(stream))&&
177+
(readableFinished||isReadable(stream)===false)
178+
){
179+
immediateResult=getEosErrored(stream);
180+
}elseif((rState&&stream.req&&stream.aborted)){
181+
immediateResult=getEosErrored(stream);
182+
}
183+
letcleanup=()=>{
184+
callback=nop;
185+
};
186+
if(immediateResult!==undefined){
187+
if(options.error!==false){
188+
stream.on('error',nop);
189+
cleanup=()=>{
190+
callback=nop;
191+
stream.removeListener('error',nop);
192+
};
193+
}
194+
}elseif(options.signal?.aborted){
195+
immediateResult=newAbortError(undefined,{cause: options.signal.reason});
196+
}
197+
if(immediateResult!==undefined&&options[kEosNodeSynchronousCallback]){
198+
ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]);
199+
returncleanup;
200+
}
201+
202+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
203+
// is a bottleneck here.
204+
callback=bindAsyncResource(callback,'STREAM_END_OF_STREAM');
205+
206+
if(immediateResult!==undefined){
207+
process.nextTick(()=>ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]));
208+
returncleanup;
209+
}
210+
211+
callback=once(callback);
212+
213+
constonlegacyfinish=()=>{
214+
if(!stream.writable){
215+
onfinish();
216+
}
217+
};
218+
111219
constonfinish=()=>{
112220
writableFinished=true;
113221
// Stream should not be destroyed here. If it is that
@@ -126,7 +234,6 @@ function eos(stream, options, callback) {
126234
}
127235
};
128236

129-
letreadableFinished=isReadableFinished(stream,false);
130237
constonend=()=>{
131238
readableFinished=true;
132239
// Stream should not be destroyed here. If it is that
@@ -149,41 +256,13 @@ function eos(stream, options, callback) {
149256
callback.call(stream,err);
150257
};
151258

152-
letclosed=isClosed(stream);
153-
154259
constonclose=()=>{
155-
closed=true;
156-
157-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
158-
159-
if(errored&&typeoferrored!=='boolean'){
160-
returncallback.call(stream,errored);
161-
}
162-
163-
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
164-
if(!isReadableFinished(stream,false))
165-
returncallback.call(stream,
166-
newERR_STREAM_PREMATURE_CLOSE());
167-
}
168-
if(writable&&!writableFinished){
169-
if(!isWritableFinished(stream,false))
170-
returncallback.call(stream,
171-
newERR_STREAM_PREMATURE_CLOSE());
172-
}
173-
174-
callback.call(stream);
175-
};
176-
177-
constonclosed=()=>{
178-
closed=true;
179-
180-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
181-
182-
if(errored&&typeoferrored!=='boolean'){
183-
returncallback.call(stream,errored);
260+
consterror=getEosOnCloseError(stream,readable,readableFinished,writable,writableFinished);
261+
if(error===null){
262+
callback.call(stream);
263+
}else{
264+
callback.call(stream,error);
184265
}
185-
186-
callback.call(stream);
187266
};
188267

189268
constonrequest=()=>{
@@ -217,30 +296,7 @@ function eos(stream, options, callback) {
217296
}
218297
stream.on('close',onclose);
219298

220-
if(closed){
221-
process.nextTick(onclose);
222-
}elseif(wState?.errorEmitted||rState?.errorEmitted){
223-
if(!willEmitClose){
224-
process.nextTick(onclosed);
225-
}
226-
}elseif(
227-
!readable&&
228-
(!willEmitClose||isReadable(stream))&&
229-
(writableFinished||isWritable(stream)===false)&&
230-
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
231-
){
232-
process.nextTick(onclosed);
233-
}elseif(
234-
!writable&&
235-
(!willEmitClose||isWritable(stream))&&
236-
(readableFinished||isReadable(stream)===false)
237-
){
238-
process.nextTick(onclosed);
239-
}elseif((rState&&stream.req&&stream.aborted)){
240-
process.nextTick(onclosed);
241-
}
242-
243-
constcleanup=()=>{
299+
cleanup=()=>{
244300
callback=nop;
245301
stream.removeListener('aborted',onclose);
246302
stream.removeListener('complete',onfinish);
@@ -255,7 +311,7 @@ function eos(stream, options, callback) {
255311
stream.removeListener('close',onclose);
256312
};
257313

258-
if(options.signal&&!closed){
314+
if(options.signal){
259315
constabort=()=>{
260316
// Keep it because cleanup removes it.
261317
constendCallback=callback;
@@ -264,23 +320,23 @@ function eos(stream, options, callback) {
264320
stream,
265321
newAbortError(undefined,{cause: options.signal.reason}));
266322
};
267-
if(options.signal.aborted){
268-
process.nextTick(abort);
269-
}else{
270-
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
271-
constdisposable=addAbortListener(options.signal,abort);
272-
constoriginalCallback=callback;
273-
callback=once((...args)=>{
274-
disposable[SymbolDispose]();
275-
originalCallback.apply(stream,args);
276-
});
277-
}
323+
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
324+
constdisposable=addAbortListener(options.signal,abort);
325+
constoriginalCallback=callback;
326+
callback=once((...args)=>{
327+
disposable[SymbolDispose]();
328+
ReflectApply(originalCallback,stream,args);
329+
});
278330
}
279331

280332
returncleanup;
281333
}
282334

283335
functioneosWeb(stream,options,callback){
336+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
337+
// is a bottleneck here.
338+
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
339+
284340
letisAborted=false;
285341
letabort=nop;
286342
if(options.signal){
@@ -339,4 +395,5 @@ function finished(stream, opts) {
339395
module.exports={
340396
eos,
341397
finished,
398+
kEosNodeSynchronousCallback,
342399
};

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 5658c63

Browse files
ikeyanaduh95
authored andcommitted
stream: align Readable.toWeb termination with eos
PR-URL: #62394 Backport-PR-URL: #64675 Reviewed-By: Mattias Buelens <mattias@buelens.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cc425bc commit 5658c63

5 files changed

Lines changed: 416 additions & 164 deletions

File tree

β€Žlib/internal/streams/end-of-stream.jsβ€Ž

Lines changed: 141 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
const{
77
Promise,
88
PromisePrototypeThen,
9+
ReflectApply,
10+
Symbol,
911
SymbolDispose,
1012
}=primordials;
1113

@@ -62,6 +64,50 @@ function bindAsyncResource(fn, type) {
6264
};
6365
}
6466

67+
/**
68+
* Returns the current stream error tracked by eos(), if any.
69+
* @param {import('stream').Stream} stream
70+
* @returns {Error | null}
71+
*/
72+
functiongetEosErrored(stream){
73+
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
74+
returntypeoferrored!=='boolean'&&errored||null;
75+
}
76+
77+
/**
78+
* Returns the error eos() would report from an immediate close, including
79+
* premature close detection for unfinished readable or writable sides.
80+
* @param {import('stream').Stream} stream
81+
* @param {boolean} readable
82+
* @param {boolean | null} readableFinished
83+
* @param {boolean} writable
84+
* @param {boolean | null} writableFinished
85+
* @returns {Error | null}
86+
*/
87+
functiongetEosOnCloseError(stream,readable,readableFinished,writable,writableFinished){
88+
consterrored=getEosErrored(stream);
89+
if(errored){
90+
returnerrored;
91+
}
92+
93+
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
94+
if(!isReadableFinished(stream,false)){
95+
returnnewERR_STREAM_PREMATURE_CLOSE();
96+
}
97+
}
98+
if(writable&&!writableFinished){
99+
if(!isWritableFinished(stream,false)){
100+
returnnewERR_STREAM_PREMATURE_CLOSE();
101+
}
102+
}
103+
104+
returnnull;
105+
}
106+
107+
// Internal only: if eos() can settle immediately, invoke the callback before
108+
// returning cleanup. Callers must tolerate cleanup yet to be assigned.
109+
constkEosNodeSynchronousCallback=Symbol('kEosNodeSynchronousCallback');
110+
65111
functioneos(stream,options,callback){
66112
if(arguments.length===2){
67113
callback=options;
@@ -74,10 +120,6 @@ function eos(stream, options, callback) {
74120
validateFunction(callback,'callback');
75121
validateAbortSignal(options.signal,'options.signal');
76122

77-
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
78-
// is a bottleneck here.
79-
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
80-
81123
if(isReadableStream(stream)||isWritableStream(stream)){
82124
returneosWeb(stream,options,callback);
83125
}
@@ -89,15 +131,6 @@ function eos(stream, options, callback) {
89131
constreadable=options.readable??isReadableNodeStream(stream);
90132
constwritable=options.writable??isWritableNodeStream(stream);
91133

92-
constwState=stream._writableState;
93-
constrState=stream._readableState;
94-
95-
constonlegacyfinish=()=>{
96-
if(!stream.writable){
97-
onfinish();
98-
}
99-
};
100-
101134
// TODO (ronag): Improve soft detection to include core modules and
102135
// common ecosystem modules that do properly emit 'close' but fail
103136
// this generic check.
@@ -106,8 +139,83 @@ function eos(stream, options, callback) {
106139
isReadableNodeStream(stream)===readable&&
107140
isWritableNodeStream(stream)===writable
108141
);
109-
110142
letwritableFinished=isWritableFinished(stream,false);
143+
letreadableFinished=isReadableFinished(stream,false);
144+
145+
constwState=stream._writableState;
146+
constrState=stream._readableState;
147+
148+
/**
149+
* @type {Error | null | undefined}
150+
* undefined: to be determined
151+
* null: no error
152+
* Error: an error occurred
153+
*/
154+
letimmediateResult;
155+
if(isClosed(stream)){
156+
immediateResult=getEosOnCloseError(
157+
stream,
158+
readable,
159+
readableFinished,
160+
writable,
161+
writableFinished,
162+
);
163+
}elseif(wState?.errorEmitted||rState?.errorEmitted){
164+
if(!willEmitClose){
165+
immediateResult=getEosErrored(stream);
166+
}
167+
}elseif(
168+
!readable&&
169+
(!willEmitClose||isReadable(stream))&&
170+
(writableFinished||isWritable(stream)===false)&&
171+
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
172+
){
173+
immediateResult=getEosErrored(stream);
174+
}elseif(
175+
!writable&&
176+
(!willEmitClose||isWritable(stream))&&
177+
(readableFinished||isReadable(stream)===false)
178+
){
179+
immediateResult=getEosErrored(stream);
180+
}elseif((rState&&stream.req&&stream.aborted)){
181+
immediateResult=getEosErrored(stream);
182+
}
183+
letcleanup=()=>{
184+
callback=nop;
185+
};
186+
if(immediateResult!==undefined){
187+
if(options.error!==false){
188+
stream.on('error',nop);
189+
cleanup=()=>{
190+
callback=nop;
191+
stream.removeListener('error',nop);
192+
};
193+
}
194+
}elseif(options.signal?.aborted){
195+
immediateResult=newAbortError(undefined,{cause: options.signal.reason});
196+
}
197+
if(immediateResult!==undefined&&options[kEosNodeSynchronousCallback]){
198+
ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]);
199+
returncleanup;
200+
}
201+
202+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
203+
// is a bottleneck here.
204+
callback=bindAsyncResource(callback,'STREAM_END_OF_STREAM');
205+
206+
if(immediateResult!==undefined){
207+
process.nextTick(()=>ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]));
208+
returncleanup;
209+
}
210+
211+
callback=once(callback);
212+
213+
constonlegacyfinish=()=>{
214+
if(!stream.writable){
215+
onfinish();
216+
}
217+
};
218+
111219
constonfinish=()=>{
112220
writableFinished=true;
113221
// Stream should not be destroyed here. If it is that
@@ -126,7 +234,6 @@ function eos(stream, options, callback) {
126234
}
127235
};
128236

129-
letreadableFinished=isReadableFinished(stream,false);
130237
constonend=()=>{
131238
readableFinished=true;
132239
// Stream should not be destroyed here. If it is that
@@ -149,41 +256,13 @@ function eos(stream, options, callback) {
149256
callback.call(stream,err);
150257
};
151258

152-
letclosed=isClosed(stream);
153-
154259
constonclose=()=>{
155-
closed=true;
156-
157-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
158-
159-
if(errored&&typeoferrored!=='boolean'){
160-
returncallback.call(stream,errored);
161-
}
162-
163-
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
164-
if(!isReadableFinished(stream,false))
165-
returncallback.call(stream,
166-
newERR_STREAM_PREMATURE_CLOSE());
167-
}
168-
if(writable&&!writableFinished){
169-
if(!isWritableFinished(stream,false))
170-
returncallback.call(stream,
171-
newERR_STREAM_PREMATURE_CLOSE());
172-
}
173-
174-
callback.call(stream);
175-
};
176-
177-
constonclosed=()=>{
178-
closed=true;
179-
180-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
181-
182-
if(errored&&typeoferrored!=='boolean'){
183-
returncallback.call(stream,errored);
260+
consterror=getEosOnCloseError(stream,readable,readableFinished,writable,writableFinished);
261+
if(error===null){
262+
callback.call(stream);
263+
}else{
264+
callback.call(stream,error);
184265
}
185-
186-
callback.call(stream);
187266
};
188267

189268
constonrequest=()=>{
@@ -217,30 +296,7 @@ function eos(stream, options, callback) {
217296
}
218297
stream.on('close',onclose);
219298

220-
if(closed){
221-
process.nextTick(onclose);
222-
}elseif(wState?.errorEmitted||rState?.errorEmitted){
223-
if(!willEmitClose){
224-
process.nextTick(onclosed);
225-
}
226-
}elseif(
227-
!readable&&
228-
(!willEmitClose||isReadable(stream))&&
229-
(writableFinished||isWritable(stream)===false)&&
230-
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
231-
){
232-
process.nextTick(onclosed);
233-
}elseif(
234-
!writable&&
235-
(!willEmitClose||isWritable(stream))&&
236-
(readableFinished||isReadable(stream)===false)
237-
){
238-
process.nextTick(onclosed);
239-
}elseif((rState&&stream.req&&stream.aborted)){
240-
process.nextTick(onclosed);
241-
}
242-
243-
constcleanup=()=>{
299+
cleanup=()=>{
244300
callback=nop;
245301
stream.removeListener('aborted',onclose);
246302
stream.removeListener('complete',onfinish);
@@ -255,7 +311,7 @@ function eos(stream, options, callback) {
255311
stream.removeListener('close',onclose);
256312
};
257313

258-
if(options.signal&&!closed){
314+
if(options.signal){
259315
constabort=()=>{
260316
// Keep it because cleanup removes it.
261317
constendCallback=callback;
@@ -264,23 +320,23 @@ function eos(stream, options, callback) {
264320
stream,
265321
newAbortError(undefined,{cause: options.signal.reason}));
266322
};
267-
if(options.signal.aborted){
268-
process.nextTick(abort);
269-
}else{
270-
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
271-
constdisposable=addAbortListener(options.signal,abort);
272-
constoriginalCallback=callback;
273-
callback=once((...args)=>{
274-
disposable[SymbolDispose]();
275-
originalCallback.apply(stream,args);
276-
});
277-
}
323+
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
324+
constdisposable=addAbortListener(options.signal,abort);
325+
constoriginalCallback=callback;
326+
callback=once((...args)=>{
327+
disposable[SymbolDispose]();
328+
ReflectApply(originalCallback,stream,args);
329+
});
278330
}
279331

280332
returncleanup;
281333
}
282334

283335
functioneosWeb(stream,options,callback){
336+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
337+
// is a bottleneck here.
338+
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
339+
284340
letisAborted=false;
285341
letabort=nop;
286342
if(options.signal){
@@ -339,4 +395,5 @@ function finished(stream, opts) {
339395
module.exports={
340396
eos,
341397
finished,
398+
kEosNodeSynchronousCallback,
342399
};

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 5658c63

Browse files
ikeyanaduh95
authored andcommitted
stream: align Readable.toWeb termination with eos
PR-URL: #62394 Backport-PR-URL: #64675 Reviewed-By: Mattias Buelens <mattias@buelens.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent cc425bc commit 5658c63

5 files changed

Lines changed: 416 additions & 164 deletions

File tree

β€Žlib/internal/streams/end-of-stream.jsβ€Ž

Lines changed: 141 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
const{
77
Promise,
88
PromisePrototypeThen,
9+
ReflectApply,
10+
Symbol,
911
SymbolDispose,
1012
}=primordials;
1113

@@ -62,6 +64,50 @@ function bindAsyncResource(fn, type) {
6264
};
6365
}
6466

67+
/**
68+
* Returns the current stream error tracked by eos(), if any.
69+
* @param {import('stream').Stream} stream
70+
* @returns {Error | null}
71+
*/
72+
functiongetEosErrored(stream){
73+
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
74+
returntypeoferrored!=='boolean'&&errored||null;
75+
}
76+
77+
/**
78+
* Returns the error eos() would report from an immediate close, including
79+
* premature close detection for unfinished readable or writable sides.
80+
* @param {import('stream').Stream} stream
81+
* @param {boolean} readable
82+
* @param {boolean | null} readableFinished
83+
* @param {boolean} writable
84+
* @param {boolean | null} writableFinished
85+
* @returns {Error | null}
86+
*/
87+
functiongetEosOnCloseError(stream,readable,readableFinished,writable,writableFinished){
88+
consterrored=getEosErrored(stream);
89+
if(errored){
90+
returnerrored;
91+
}
92+
93+
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
94+
if(!isReadableFinished(stream,false)){
95+
returnnewERR_STREAM_PREMATURE_CLOSE();
96+
}
97+
}
98+
if(writable&&!writableFinished){
99+
if(!isWritableFinished(stream,false)){
100+
returnnewERR_STREAM_PREMATURE_CLOSE();
101+
}
102+
}
103+
104+
returnnull;
105+
}
106+
107+
// Internal only: if eos() can settle immediately, invoke the callback before
108+
// returning cleanup. Callers must tolerate cleanup yet to be assigned.
109+
constkEosNodeSynchronousCallback=Symbol('kEosNodeSynchronousCallback');
110+
65111
functioneos(stream,options,callback){
66112
if(arguments.length===2){
67113
callback=options;
@@ -74,10 +120,6 @@ function eos(stream, options, callback) {
74120
validateFunction(callback,'callback');
75121
validateAbortSignal(options.signal,'options.signal');
76122

77-
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
78-
// is a bottleneck here.
79-
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
80-
81123
if(isReadableStream(stream)||isWritableStream(stream)){
82124
returneosWeb(stream,options,callback);
83125
}
@@ -89,15 +131,6 @@ function eos(stream, options, callback) {
89131
constreadable=options.readable??isReadableNodeStream(stream);
90132
constwritable=options.writable??isWritableNodeStream(stream);
91133

92-
constwState=stream._writableState;
93-
constrState=stream._readableState;
94-
95-
constonlegacyfinish=()=>{
96-
if(!stream.writable){
97-
onfinish();
98-
}
99-
};
100-
101134
// TODO (ronag): Improve soft detection to include core modules and
102135
// common ecosystem modules that do properly emit 'close' but fail
103136
// this generic check.
@@ -106,8 +139,83 @@ function eos(stream, options, callback) {
106139
isReadableNodeStream(stream)===readable&&
107140
isWritableNodeStream(stream)===writable
108141
);
109-
110142
letwritableFinished=isWritableFinished(stream,false);
143+
letreadableFinished=isReadableFinished(stream,false);
144+
145+
constwState=stream._writableState;
146+
constrState=stream._readableState;
147+
148+
/**
149+
* @type {Error | null | undefined}
150+
* undefined: to be determined
151+
* null: no error
152+
* Error: an error occurred
153+
*/
154+
letimmediateResult;
155+
if(isClosed(stream)){
156+
immediateResult=getEosOnCloseError(
157+
stream,
158+
readable,
159+
readableFinished,
160+
writable,
161+
writableFinished,
162+
);
163+
}elseif(wState?.errorEmitted||rState?.errorEmitted){
164+
if(!willEmitClose){
165+
immediateResult=getEosErrored(stream);
166+
}
167+
}elseif(
168+
!readable&&
169+
(!willEmitClose||isReadable(stream))&&
170+
(writableFinished||isWritable(stream)===false)&&
171+
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
172+
){
173+
immediateResult=getEosErrored(stream);
174+
}elseif(
175+
!writable&&
176+
(!willEmitClose||isWritable(stream))&&
177+
(readableFinished||isReadable(stream)===false)
178+
){
179+
immediateResult=getEosErrored(stream);
180+
}elseif((rState&&stream.req&&stream.aborted)){
181+
immediateResult=getEosErrored(stream);
182+
}
183+
letcleanup=()=>{
184+
callback=nop;
185+
};
186+
if(immediateResult!==undefined){
187+
if(options.error!==false){
188+
stream.on('error',nop);
189+
cleanup=()=>{
190+
callback=nop;
191+
stream.removeListener('error',nop);
192+
};
193+
}
194+
}elseif(options.signal?.aborted){
195+
immediateResult=newAbortError(undefined,{cause: options.signal.reason});
196+
}
197+
if(immediateResult!==undefined&&options[kEosNodeSynchronousCallback]){
198+
ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]);
199+
returncleanup;
200+
}
201+
202+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
203+
// is a bottleneck here.
204+
callback=bindAsyncResource(callback,'STREAM_END_OF_STREAM');
205+
206+
if(immediateResult!==undefined){
207+
process.nextTick(()=>ReflectApply(callback,stream,immediateResult===null ? [] : [immediateResult]));
208+
returncleanup;
209+
}
210+
211+
callback=once(callback);
212+
213+
constonlegacyfinish=()=>{
214+
if(!stream.writable){
215+
onfinish();
216+
}
217+
};
218+
111219
constonfinish=()=>{
112220
writableFinished=true;
113221
// Stream should not be destroyed here. If it is that
@@ -126,7 +234,6 @@ function eos(stream, options, callback) {
126234
}
127235
};
128236

129-
letreadableFinished=isReadableFinished(stream,false);
130237
constonend=()=>{
131238
readableFinished=true;
132239
// Stream should not be destroyed here. If it is that
@@ -149,41 +256,13 @@ function eos(stream, options, callback) {
149256
callback.call(stream,err);
150257
};
151258

152-
letclosed=isClosed(stream);
153-
154259
constonclose=()=>{
155-
closed=true;
156-
157-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
158-
159-
if(errored&&typeoferrored!=='boolean'){
160-
returncallback.call(stream,errored);
161-
}
162-
163-
if(readable&&!readableFinished&&isReadableNodeStream(stream,true)){
164-
if(!isReadableFinished(stream,false))
165-
returncallback.call(stream,
166-
newERR_STREAM_PREMATURE_CLOSE());
167-
}
168-
if(writable&&!writableFinished){
169-
if(!isWritableFinished(stream,false))
170-
returncallback.call(stream,
171-
newERR_STREAM_PREMATURE_CLOSE());
172-
}
173-
174-
callback.call(stream);
175-
};
176-
177-
constonclosed=()=>{
178-
closed=true;
179-
180-
consterrored=isWritableErrored(stream)||isReadableErrored(stream);
181-
182-
if(errored&&typeoferrored!=='boolean'){
183-
returncallback.call(stream,errored);
260+
consterror=getEosOnCloseError(stream,readable,readableFinished,writable,writableFinished);
261+
if(error===null){
262+
callback.call(stream);
263+
}else{
264+
callback.call(stream,error);
184265
}
185-
186-
callback.call(stream);
187266
};
188267

189268
constonrequest=()=>{
@@ -217,30 +296,7 @@ function eos(stream, options, callback) {
217296
}
218297
stream.on('close',onclose);
219298

220-
if(closed){
221-
process.nextTick(onclose);
222-
}elseif(wState?.errorEmitted||rState?.errorEmitted){
223-
if(!willEmitClose){
224-
process.nextTick(onclosed);
225-
}
226-
}elseif(
227-
!readable&&
228-
(!willEmitClose||isReadable(stream))&&
229-
(writableFinished||isWritable(stream)===false)&&
230-
(wState==null||wState.pendingcb===undefined||wState.pendingcb===0)
231-
){
232-
process.nextTick(onclosed);
233-
}elseif(
234-
!writable&&
235-
(!willEmitClose||isWritable(stream))&&
236-
(readableFinished||isReadable(stream)===false)
237-
){
238-
process.nextTick(onclosed);
239-
}elseif((rState&&stream.req&&stream.aborted)){
240-
process.nextTick(onclosed);
241-
}
242-
243-
constcleanup=()=>{
299+
cleanup=()=>{
244300
callback=nop;
245301
stream.removeListener('aborted',onclose);
246302
stream.removeListener('complete',onfinish);
@@ -255,7 +311,7 @@ function eos(stream, options, callback) {
255311
stream.removeListener('close',onclose);
256312
};
257313

258-
if(options.signal&&!closed){
314+
if(options.signal){
259315
constabort=()=>{
260316
// Keep it because cleanup removes it.
261317
constendCallback=callback;
@@ -264,23 +320,23 @@ function eos(stream, options, callback) {
264320
stream,
265321
newAbortError(undefined,{cause: options.signal.reason}));
266322
};
267-
if(options.signal.aborted){
268-
process.nextTick(abort);
269-
}else{
270-
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
271-
constdisposable=addAbortListener(options.signal,abort);
272-
constoriginalCallback=callback;
273-
callback=once((...args)=>{
274-
disposable[SymbolDispose]();
275-
originalCallback.apply(stream,args);
276-
});
277-
}
323+
addAbortListener??=require('internal/events/abort_listener').addAbortListener;
324+
constdisposable=addAbortListener(options.signal,abort);
325+
constoriginalCallback=callback;
326+
callback=once((...args)=>{
327+
disposable[SymbolDispose]();
328+
ReflectApply(originalCallback,stream,args);
329+
});
278330
}
279331

280332
returncleanup;
281333
}
282334

283335
functioneosWeb(stream,options,callback){
336+
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
337+
// is a bottleneck here.
338+
callback=once(bindAsyncResource(callback,'STREAM_END_OF_STREAM'));
339+
284340
letisAborted=false;
285341
letabort=nop;
286342
if(options.signal){
@@ -339,4 +395,5 @@ function finished(stream, opts) {
339395
module.exports={
340396
eos,
341397
finished,
398+
kEosNodeSynchronousCallback,
342399
};

0 commit comments

Comments
Β (0)