-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wait for stream to finish sending before shutdown #3291
Comments
Have you considered that data can not only be lost in a buffer, but also when in transfer? Waiting until the data has been sent out doesn't help you, as the packets might still be lost while in flight. If you want a clean shutdown with a guarantee that all data has been transmitted, you'll need to introduce a signal on the application layer. |
Thanks for your reply and sorry for being imprecise. Sure, yes I've considered that packets might lost -- by "transmitted", I meant "transmitted and acknowledged". |
That won’t work either. The ACK might be lost. |
Ok sure, if the ACKs are lost, then the proposed |
I disagree. The way that streams are used is highly dependent on the application, and what works well for one application doesn’t work for the other. |
This must be a misunderstanding, the suggested API does not "break" in case of packet loss. Perhaps the function names that I suggested are misleading? The idea is that it would allow to wait for the internal buffer to become empty, which, indeed, is something that may never happen. I don't understand why you think this means it "breaks" -- with this interpretation
Without this API, there does not appear to be a way to use quic-go as a "drop-in" replacement for TCP without jumping through hoops to ensure correct termination. |
You're right. The sender will send PTO packets to force the acknowledgement.
I think you're right. I wasn't aware how SO_LINGER works in detail. Here's a good explanation: https://ndeepak.com/posts/2016-10-21-tcprst/
We would have to be very explicit that the context has to be canceled at some point. Otherwise a malicious peer could just refuse to acknowledge STREAM data it has received, and make us wait forever. |
Thanks for reconsidering this! In the meantime, I took a cursory look at how this is handled in a few other quic implementations; None of these approaches really fit quic-go, but perhaps it does suggest a different way to model this: instead of providing a stream.Write(data)
stream.Close()
select {
case <-someCtx.Done(): // or timer, or whatever
// timeout
case <-stream.Drained():
// ok
} Or alternatively, the ~same thing on the session level. This would make the blocking behavior much more obvious, perhaps reducing the risk that a caller accidentally passes a context that's never cancelled. It also seems to less ergonomic for what I imagine are the typical uses cases though, so I think my personal preference would still be with the initial suggestion. |
@marten-seemann Hi, any update on this enhancement? i run into the same issue |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
I like the idea of a notification that the stream has finished sending, but that will not help with a graceful connection close. This is because QUIC doesn't specify any behavior with regards to received data on streams on receiving |
An application using quic-go currently has no reliable way to wait for a/all quic sessions/streams to finish sending before shutting down.
When an application writes on a stream, the data is buffered internally in quic-go, similar TCP. If the application then shuts down (or closes the quic session or closes the UDP socket) before all the buffered data has actually been transmitted, this will simply be lost.
Side note: with TCP, this issue (usually) does not occur because the kernel keeps working on this send buffer after the application has shut down (and there is also the option
SO_LINGER
to control explicitly blockingclose
calls until all data has been transmitted). In QUIC, this is all in user space, so obviously once the application has shut down, the buffered data will not be transmitted.Side note: for HTTP/3 this issue does not appear to occur by virtue of how QUIC is used. The server is long-lived to begin with, so any data it sends will reliably be transmitted (but CloseGracefully might be affected). The server usually replies after reading the full request and the (short lived) client then reads the full server reply. Thus, the client's data will be fully transmitted before shutting down.
Demonstration / Reproduction:
main.go
Run:
go run main.go -s quic-go/internal/testdata/cert.pem -key quic-go/internal/testdata/priv.key
go run main.go
Note that this is issue inherently timing dependent. Sometimes the server reports success (full data read and no error), or reading the full data but a timeout (the FIN for the stream was not sent/received).
Notes:
time.Sleep()
in there after closing the stream in the client, this example will work reliably.netcat -l
).Proposal:
Add an API to allow an application to block until all data on a stream / session has been sent before shutting down.
This could be either on the level of a
SendStream
or aSession
(or both). For the shutdown issue described above, something on the level of the Session might be more convenient. On the other hand, having such a functionality on the level of Streams could facilitate other advanced usage.Perhaps the most straight forward way to add this to the interface would be:
Session.CloseWithErrorSync(context.Context, ApplicationErrorCode, string) error
: this blocks until all buffered data has been transmitted and then closes the session.Stream.CloseSync(context.Context) error
: close the stream and then wait for all buffered data to be transmitted.The text was updated successfully, but these errors were encountered: