Skip to content
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

[QUIC] API QuicStream #71969

Merged
merged 18 commits into from
Jul 13, 2022
Merged

[QUIC] API QuicStream #71969

merged 18 commits into from
Jul 13, 2022

Conversation

ManickaP
Copy link
Member

@ManickaP ManickaP commented Jul 11, 2022

Fixes #69675

This PR is based on #71783 so it shows more changes than it actually contains, see https://github.com/ManickaP/runtime/compare/mapichov/quic-connection...ManickaP:runtime:mapichov/quic-stream?expand=1 for the actual diff.

ATM depends on: microsoft/msquic#2883 but AFAIK only one outerloop test is failing because of this (the one testing stream shutdown due to idle connection). Depending on severity of this and speed on that msquic PR, I may temporarily re-introduce connection state from:

// TODO: remove once/if https://github.com/microsoft/msquic/pull/2872 is merged
internal sealed class State
{
public long AbortErrorCode = -1;
}
private State _state = new State();

Also I'll be filling in comments, not everything is covered.

Kept in draft until API is approved.

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost
Copy link

ghost commented Jul 11, 2022

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

Issue Details

Fixes #69675

This PR is based on #71783 so it shows more changes than it actually contains.

ATM depends on: microsoft/msquic#2883 but AFAIK only one outerloop test is failing because of this (the one testing stream shutdown due to idle connection). Depending on severity of this and speed on that msquic PR, I may temporarily re-introduce connection state from:

// TODO: remove once/if https://github.com/microsoft/msquic/pull/2872 is merged
internal sealed class State
{
public long AbortErrorCode = -1;
}
private State _state = new State();

Also I'll be filling in comments, not everything is covered.

Kept in draft until API is approved.

Author: ManickaP
Assignees: -
Labels:

new-api-needs-documentation, area-System.Net.Quic

Milestone: -

// Concurrent call, this one lost the race.
if (!_receiveTcs.TryGetValueTask(out ValueTask valueTask, this, cancellationToken))
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "read"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this inside do...while ? One iteration can copy, and the following can throw?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop can iterate at most twice. First, it tries to copy from the buffer, if it succeeds (data are already there) it will break out of the loop in while (... totalCopied == 0);. If no data are available in the buffer, the await valueTask.ConfigureAwait(false); will wait on the next RECEIVE event and once it arrives it will repeat the loop with copying, copy something (or learn about FIN flag) and break out of the loop.

The valueTask must be awaited each time it's retrieved, even if it's completed and it seems pointless - e.g.: _receiveTcs.TrySetResult();, since it's backed up by MRVTS which keeps "version" and the await will reset it (increment it).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment like "this will loop at most twice" would be nice :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't understand how we would overcome the race when - between the first and the second iteration - a parallel read could come and "steal" the arrived data? What would happen to the first read? It seems like it could either throw or - would it start waiting on data to arrive again?


public void Shutdown() => _provider.Shutdown();
// TODO: memory leak if not disposed
_sendBuffers.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we make sure send buffers are not disposed in the middle of send operation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By awaiting the valueTask above which waits on SHUTDOWN_COMPLETE which is the last event on the stream and means both side are closed, so no send operation is in progress.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment for this please? This is something I am going to forget very soon :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment 3 lines above, at the valueTask await.

@ManickaP ManickaP force-pushed the mapichov/quic-stream branch 3 times, most recently from ac8f5ca to ecd1e28 Compare July 12, 2022 20:43
_stream.Shutdown();
await _stream.ShutdownCompleted().ConfigureAwait(false);
Dispose();
_stream.CompleteWrites();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer do the ShutdownCompleted because we expect that in Dispose? Is there reason to delay that if we know this is final?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delay what exactly? DisposeAsync is controlled from the calling code. In most cases the tests are just doing this:

await using Http3LoopbackStream stream = await connection.AcceptRequestStreamAsync();
await stream.HandleRequestAsync();

And in some cases, the tests are playing with postponing the disposal to later, doing cancellations etc.

Potentially we could replace it with await Task.WhenAll(_stream.WritesClosed, _stream.ReadsClosed); which should correspond to SHUTDOWN_COMPLETE give or take. But I haven't experience any issues with the state as-is in this PR, so I don't particularly see reason to change it. After all, this (meaning as-is now) will be usage pattern done by the lib consumers.

Copy link
Member

@wfurt wfurt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks ok to me. I wish we have better names for some of the handles but that is ok.
It seems like we also break the build/tests in few places and that should be fixed before merging.

Copy link
Member

@rzikm rzikm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good modulo existing comments

_abortErrorCode = (long)data.ErrorCode;
_state.AbortErrorCode = _abortErrorCode;
_acceptQueue.Writer.TryComplete(ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetConnectionAbortedException(_abortErrorCode)));
_acceptQueue.Writer.TryComplete(ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetConnectionAbortedException((long)data.ErrorCode)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we store the error code anywhere? it might be useful during dump investigation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll stay set in the Exception in the _acceptQueue, so it's available, although less conveniently. Do you want me to reintroduce it? Is it something you used while doing investigations?

// Concurrent call, this one lost the race.
if (!_receiveTcs.TryGetValueTask(out ValueTask valueTask, this, cancellationToken))
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "read"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment like "this will loop at most twice" would be nice :)


public void Shutdown() => _provider.Shutdown();
// TODO: memory leak if not disposed
_sendBuffers.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment for this please? This is something I am going to forget very soon :D

@ManickaP
Copy link
Member Author

All comments should be answered, only waiting on CI.

await valueTask.ConfigureAwait(false);

// This is the last read, finish even despite not copying anything.
if (complete)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand correctly, that complete can only become true in "synchronous" case? Meaning, we didn't actually wait for anything in valueTask above

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I'd formulate it differently: if complete was reported as true then the valueTask will complete synchronously. Note that the valueTask might have been completed (whether temporarily or finally) before it got to _receiveTcs.TrySetResult(final: true);.

@ManickaP
Copy link
Member Author

One failure unrelated: #71233
One failure related (windows), reproduced locally, fixed and tested locally in a tight loop.
--> Merging now.

@ManickaP ManickaP merged commit af43652 into dotnet:main Jul 13, 2022
@ManickaP ManickaP deleted the mapichov/quic-stream branch July 13, 2022 18:02
ManickaP added a commit to ManickaP/runtime that referenced this pull request Jul 13, 2022
* Quic stream API surface

* Fixed test compilation

* Fixed http test compilation

* HttpLoopbackConnection Dispose -> DisposeAsync

* QuicStream implementation

* Fixed some tests

* Fixed all QUIC and HTTP tests

* Fixed exception type for stream closed by connection close

* Feedback

* Fixed WebSocket.Client test build

* Feedback, test fixes

* Fixed build on framework and windows

* Fixed winhandler test

* Swap variable based on order in defining class

* Post merge fixes

* Feedback and build

* Reverted connection state to pass around abort error code

* Fixed exception type.
@ManickaP
Copy link
Member Author

@JamesNK this will need your reaction as well. This contains the stream changes, most interesting will be the ReadsClosed, WritesClosed tasks.

carlossanlop pushed a commit that referenced this pull request Jul 13, 2022
…72031) (#72106)

* [QUIC] API QuicConnection (#71783)

* Listener comment; PreviewFeature attribute

* Feedback

* QuicConnection new API including compilable implementation

* Fixed logging

* Fixed S.N.Quic and S.N.Http tests

* Options now correspond to the issue

* Feedback

* Comments, PreviewFeature attribute and RemoteCertificate disposal.

* Preview feature attribute is assembly wide

* Some typos

* Fixed test with certificate

* Default values as constants

* Event handlers split into methods called via switch expression.

* Some more comments

* Unified unsafe usage

* Fixed some more tests

* Cleaned up some exceptions and resource strings.

* Feedback

* Latest greatest API proposal.

* Fixed Http solution

* Feedback

* [QUIC] API QuicStream (#71969)

* Quic stream API surface

* Fixed test compilation

* Fixed http test compilation

* HttpLoopbackConnection Dispose -> DisposeAsync

* QuicStream implementation

* Fixed some tests

* Fixed all QUIC and HTTP tests

* Fixed exception type for stream closed by connection close

* Feedback

* Fixed WebSocket.Client test build

* Feedback, test fixes

* Fixed build on framework and windows

* Fixed winhandler test

* Swap variable based on order in defining class

* Post merge fixes

* Feedback and build

* Reverted connection state to pass around abort error code

* Fixed exception type.

* [QUIC] System.Net.Quic API made public (#72031)

* System.Net.Quic removed from ASP transport package and made part of SDK ref

* Removed manual references to System.Net.Quic.csproj
@karelz karelz added this to the 7.0.0 milestone Jul 19, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[API Proposal]: [QUIC] QuicStream
5 participants