dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.27k stars 4.73k forks source link

[QUIC] Bidirectional stream cannot start with receiving #80687

Open ManickaP opened 1 year ago

ManickaP commented 1 year ago

Since we do not announce a newly opened stream until we send some data, the following scenario will never work:

var outgoingStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional);
var buffer = new byte[100];
await outgoingStream.ReadAsync(buffer);
...

I do concur that this is not very logical use of the stream, but after quick peruse, I haven't found anything against this in the spec (I might have missed it though).

Thoughts on this? @wfurt, @CarnaViire, @rzikm, @nibanks

ghost commented 1 year ago

Tagging subscribers to this area: @dotnet/ncl See info in area-owners.md if you want to be subscribed.

Issue Details
Since we do not announce a newly opened stream until we send some data, the following scenario will never work: ```C# var outgoingStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); var buffer = new byte[100]; await outgoingStream.ReadAsync(buffer); ... ``` I do concur that this is not very logical use of the stream, but after quick peruse, I haven't found anything against this in the spec (I might have missed it though). Thoughts on this? @wfurt, @CarnaViire, @rzikm, @nibanks
Author: ManickaP
Assignees: -
Labels: `untriaged`, `area-System.Net.Quic`
Milestone: -
rzikm commented 1 year ago

The RFC says that the stream is opened by sending the first STREAM frame for it. It is a bit unfortunate/misleading that our Open*StreamAsync method does not really open the stream as RFC defines it :D. We can force open the stream by sending an empty STREAM frame when opening the stream by passing an appropriate flag to MsQuic (it might be interesting to be able to do this also later by calling Flush, I am not sure if MsQuic supports that)

From a perspective of a protocol built on top of QUIC, it seems weird to open a stream and wait for data on it. Looking at it from the other side: why should peer have to wait for us to open a stream so that it can send data when it can open its own stream and start sending immediately?

I see a few options:

  1. Always force open the stream when opening it - I believe that if the user queues data quickly enough, it will still go out as a single STREAM frame, but there is no guarantee and it might cause more datagrams to be sent as a result.
  2. Throw an exception in cases like above to prevent user being stuck in self-deadlock.
  3. Lazily force-open the stream in cases like above (if supported by MsQuic).

Personally, I like 2 and 3.

nibanks commented 1 year ago

Sounds like you might want the QUIC_STREAM_START_FLAG_IMMEDIATE flag:

QUIC_STREAM_START_FLAG_IMMEDIATE            = 0x0001,   // Immediately informs peer that stream is open.

This is for cases where you want to immediately send out that zero-length stream frame to inform the peer.

Though, another interesting angle to consider here, is that even if you don't do this, but if you then open and start another stream after it, the act of starting that other stream implicitly informs the peer of the first stream's creation; so you could then start receiving on it...

ManickaP commented 1 year ago

Apart from QUIC_STREAM_START_FLAG_IMMEDIATE, is there other way to notify the peer about the stream? E.g. by issuing 0-byte send?

nibanks commented 1 year ago

Yes. I believe you can do a 0-byte send with the QUIC_SEND_FLAG_START flag.

nibanks commented 1 year ago

Actually, after looking at the code, if you've previously started the stream, I think sending a 0-length send, even with the "start" flag, won't do anything.