You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
stream: certain sequence of Writable#write and Writable#end will not end stream #29758
I ran into a problem where certain Connection: close HTTP responses were not being closed, when the response is a Content-Length response (as opposed to a chunked response), see #29609 and #29649 for background.
I've been able to isolate this bug to a problem in "stream" and/or the duplex pair implementation (used by the "tls" module and in tests):
As far as I've been able to tell so far the second serverSidewrite will never complete because the clientSide doesn't read it out for whatever reason. Will try to dig further later tonight.
Also it seems to be related to the fact that it's an empty string. A quick fix would be to simply make empty buffers and strings a noop in Writable.write and Readable.push. However, I'm still very interested in whats going wrong here.
Also I'm not 100% convinced this is related to your http issue. But we can get to that. If I give you a patch can you build Node and run your HTTP case to see if it solves it?
As a result the write of the empty chunk does not complete (the callback is not called) and this prevents _final() from being called and from pushing the null chunk on the client.
This is consistent with net.Socket behavior but I'm not sure if it masks a bug in Readable.prototype.read() implementation.
For me it feels like it is masking a more fundamental problem. #29762 is my take on it but I'm out on deep water.
It looks to me like that we have logic to trigger read even on empty chunks, however that logic breaks down if the stream received EOF in the same tick.
@ronag The HTTP issue is being caused because the callback of Writable#write('', 'latin1', callback) is never called, so I think if this case is fixed so will the HTTP case. But either way, this problem will affect the HTTP one.
@lpinca I'll start using your patch to avoid empty push()es as a workaround, but yeah, streams should be able to handle empty pushes like this; or if this is a requirement, it should be documented somewhere.
HTTP is writing an empty string to "flush" the stream, is this strictly necessary?
Activity
ronag commentedon Sep 29, 2019
As far as I've been able to tell so far the second
serverSidewritewill never complete because theclientSidedoesn't read it out for whatever reason. Will try to dig further later tonight.Also it seems to be related to the fact that it's an empty string. A quick fix would be to simply make empty buffers and strings a noop in
Writable.writeandReadable.push. However, I'm still very interested in whats going wrong here.Also I'm not 100% convinced this is related to your http issue. But we can get to that. If I give you a patch can you build Node and run your HTTP case to see if it solves it?
lpinca commentedon Sep 29, 2019
From a quick look it seems that
_read()is not called for the empty chunk.node/test/common/duplexpair.js
Lines 16 to 22 in 82f89ec
As a result the write of the empty chunk does not complete (the callback is not called) and this prevents
_final()from being called and from pushing thenullchunk on the client.node/test/common/duplexpair.js
Lines 31 to 35 in 82f89ec
This is confirmed by the fact that
'finish'is not emittedIn #29649 it works with
net.Socketbecause empty chunks are discarded.node/lib/internal/stream_base_commons.js
Lines 196 to 198 in 82f89ec
A possible way to fix the issue is to not push empty chunks
This is consistent with
net.Socketbehavior but I'm not sure if it masks a bug inReadable.prototype.read()implementation.lpinca commentedon Sep 29, 2019
cc: @nodejs/streams
ronag commentedon Sep 29, 2019
For me it feels like it is masking a more fundamental problem. #29762 is my take on it but I'm out on deep water.
It looks to me like that we have logic to trigger
readeven on empty chunks, however that logic breaks down if the stream received EOF in the same tick.awwright commentedon Sep 29, 2019
@ronag The HTTP issue is being caused because the callback of
Writable#write('', 'latin1', callback)is never called, so I think if this case is fixed so will the HTTP case. But either way, this problem will affect the HTTP one.@lpinca I'll start using your patch to avoid empty
push()es as a workaround, but yeah, streams should be able to handle empty pushes like this; or if this is a requirement, it should be documented somewhere.HTTP is writing an empty string to "flush" the stream, is this strictly necessary?
node/lib/_http_outgoing.js
Lines 747 to 748 in db706da
3 remaining items
awwright commentedon Sep 29, 2019
@ronag Seems to solve the issue for me, great work!
Work around a Node.js bug
test: add test for HTTP server response with Connection: close
stream: do not deadlock duplexpair
test: add test for HTTP server response with Connection: close
Do not deadlock on empty chunks
Do not deadlock on empty chunks
stream: do not deadlock duplexpair