Open sehrgut opened 3 years ago
It looks to me like the problem is that the end
event does not cause the async iteration to end. It's hard to tell from that log, but my guess is that it is waiting for the close
event, which wouldn't be emitted on a Duplex stream until some time after both sides have called the end
method.
Regarding tryShutdown
, the purpose of that method is to prevent new requests from starting and to wait for existing open requests to end. At the point when you call it, that request has ended, so it's OK for tryShutdown
to finish quickly. The fact that there may also be some local asynchronous cleanup work doesn't really matter.
stream.on("end") notify just the end of the readable part of the duplex stream, while the writable stream is paused (not the expected behaviour) . To force close even the writable stream is needed ta call stream.end() inside the stream.on("end") event. It's really ugly but it works.
Problem description
A server consuming a bidirectional (Duplex) stream using an async iterator and calling
call.end()
after the iterator loop completes leaves the client hanging open. The only workaround is to add anend
event handler on the server stream, and callcall.end()
from there.Reproduction steps
biditest.proto
to./proto/generated
:Environment
Additional context
The Server1 instance uses the workaround, checking for the end of the source stream by listening to the
end
event, and ending the sink stream at that point. This successfully ends the stream for the client. The Server2 instance uses a pure async iterator implementation, which I would argue is a "least astonishment" implementation. After await returns from the async iterator, implying the source stream has ended, it callscall.end()
, ending the sink stream. This ought to end the stream for the client, but instead the client does not end until much later, withDEADLINE_EXCEEDED
.The completion of an async iterator on a
Readable
stream should indicate the source stream has ended. At that point, if theReadable
was aDuplex
, calling theend()
method should end the sink stream. Using the stream event interface alongside the async iterator should not be necessary.As an aside, note that when pipelined like this the ending of Server1's async iterator occurs significantly AFTER Server2 has started, which happens within the server1
tryShutdown
callback. Since the async iterator is occurring inside the async service method, it seems likely thattryShutdown
is completing before it ought to; since this handler should have to complete beforetryShutdown
ends the server and calls its callback. I haven't investigated this in-depth, since I only discovered it while creating this repro; but perhaps it ought to be another separate bug report. I'm mentioning it here on the chance that it points to a flaw in async service method handling that could be causing the main bug.