-
-
Notifications
You must be signed in to change notification settings - Fork 401
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
fix: don't fail when finish
is called on a stopped stream
#1699
Conversation
8a0e5d7
to
ccf7794
Compare
I added to commits that make the test pass. Let me know if this fix is okay :) |
f2acae6
to
52a83cf
Compare
Seems reasonable to me! |
Thanks for the PR! I'd like to discuss the motivation in some more depth. My knee-jerk reaction is discomfort with the presumptions this makes about the application's interests, and I wonder if your requirements could be addressed with a different pattern at the application layer instead.
The intention of the current API is that, if you don't want to stop incoming streams, then you always read them to the end, even if application-layer reasoning tells you to expect no further data.
What cases do you feel
This seems like a separate concern from whether streams stopped during
Returning an error if and only if we haven't received some ACKs for the stream is extremely fragile. Packet loss or reordering might cause the |
Thank you for the comment. I'll try to expand a bit.
In The issue we had in libp2p/rust-libp2p#4747 was resolved by not calling It just doesn't seem correct to fail closing the stream even though everything worked correctly and we just had a timing issue with the remote on who sent what when.
I am not sure this is true.
(4) is what I think should not fail even if the remote has already stopped the stream. There cannot be any missing ACKs because the server would not have responded if it didn't receive the entire request from (1). If we did in fact still send some data that the server hasn't acked yet, then closing should fail! In this case however, the two peers don't seem to agree on the protocol because the server apparently did not expect further data to be sent and dropped the stream without continuing to read. |
Could you instead ignore all errors from Note that even a successful
Successful receipt of a server's application-layer response does not guarantee successful receipt of all ACKs covering application data that prompted that response. This information may have been carried in separate packets. For clarity, note that your example illustrates a particular bidirectional request/response pattern. Application protocols routinely employ other patterns, which may have fewer round-trips. For example, if you open, write to, and immediately finish a unidirectional stream, and the peer stops that stream, it is impossible to reliably predict whether or not an error will be raised under this PR's changes, because packets carrying ACK frames can easily be reordered or lost when the STOP_SENDING frame is received. |
Thank you for the clarification! It seems that the safe thing to do is to instantly close the stream once we are done with writing before we start reading data from the remote. That should make this race condition go away. Still, it leaves me with an uneasy feeling that it matters when I close my stream. My argument would be that it shouldn't matter. Perhaps Perhaps the last part is interesting to debate: Why is it an "error" to |
That's a more conventional pattern, though if the peer stops the stream immediately after reading data, and the connection was very low latency, you could still theoretically race.
This would be less of a footgun, and it's tempting. Maybe even make it synchronous, and thereby prevent similarly error-prone application sensitivity to transport-layer ACKs. This places it in similar territory to a successful Our intention was to support application protocols in which
This is a good thought. On review of the API, I think any form of async, data-bearing |
Sounds good! |
I really like this! So if I understand you correctly: We essentially design Looking at the API though, I'd have a different question: Why do we expose the |
Yep!
Per the doc comments, |
But didn't we just conclude that such an error should only be returned from awaiting What if we say instead:
This means if I really care that a stream finished gracefully, I first call I don't know the internals that well though. Will calling |
To be clear, it's usually a "you're calling methods in a nonsensical order" error.
Maybe it's useful for Conversely,
It feels a bit weird that
For this to work as written, we should probably replace
Stream resets are advisory, and a peer that has received a RESET_STREAM frame from you probably won't bother to send STOP_SENDING, since you've already declared intent not to send. A stream is "actually reset" whenever you say it is, and what the peer makes of that is up to them. |
Perhaps a
So what happens when I call |
Yep, exactly! |
Okay, I might have a stab at this next week :) |
Closing in favor of #1840, where I've implemented something like what we discussed here, since the next release is very close. Feedback welcome! |
At the moment, calling
finish
on a stream that has been stopped by the remote fails. This can lead to weird race conditions in application protocols. I added a test that showcases this. Depending on where you place thesend_stream.finish
in the client, the test either succeeds or fails. In other words, if therecv_stream
at the opposite side is still alive, callingfinish
will work but if it is not,finish
will fail.In a real-world setting, we cannot influence how fast the remote is in processing your data and / or when they drop (i.e. stop) the stream. I think
quinn
should not failfinish
if the other party has already stopped the stream. Instead, we should assume that they've read all the data they expected and thus no longer need the stream.Unfortunately, the only workaround for this issue is to not call
finish
at all. Whilst that may fine for some usecases, I'd argue that it is not very clean andfinish
should in fact be idempotent.To fix this issue, we check whether we all data we sent has been acknowledged and in that case, exit
finish
gracefully, pretending that the remote acked ourFIN
.