-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Expose and roll out ValueTask extensibility #27497
Conversation
e4a7364
to
a341b2e
Compare
@@ -838,7 +838,7 @@ private void WriteToBuffer(ReadOnlyMemory<byte> source) | |||
} | |||
} | |||
|
|||
private Task WriteWithoutBufferingAsync(ReadOnlyMemory<byte> source) | |||
private ValueTask WriteWithoutBufferingAsync(ReadOnlyMemory<byte> source) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you change this one? It probably doesn't matter, but I'm just curious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, I see why.
@@ -1171,7 +1171,7 @@ private void ReadFromBuffer(Span<byte> buffer) | |||
Debug.Assert(count <= _readLength - _readOffset); | |||
|
|||
if (NetEventSource.IsEnabled) Trace($"Copying {count} bytes to stream."); | |||
await destination.WriteAsync(_readBuffer, _readOffset, count, cancellationToken).ConfigureAwait(false); | |||
await destination.WriteAsync(new ReadOnlyMemory<byte>(_readBuffer, _readOffset, count), cancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: could use RemainingBuffer here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemainingBuffer
is new ReadOnlyMemory<byte>(_readBuffer, _readOffset, _readLength - _readOffset)
whereas this is new ReadOnlyMemory<byte>(_readBuffer, _readOffset, count)
. I could do RemainingBuffer.Slice(0, count)
, but I figured why incur the extra slicing when I could just create the right size to begin with.
We're not using the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WebSocket changes LGTM
Yes we are. |
Oh right, you've been changing my code :) |
34a565f
to
ccfd967
Compare
t = lastTask != null && lastTask.Result == bytesTransferred ? | ||
lastTask : | ||
(saea._successfullyCompletedTask = Task.FromResult(bytesTransferred)); | ||
t = Task.FromResult(bytesTransferred); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update comment here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix
@@ -119,5 +119,8 @@ | |||
<ItemGroup> | |||
<EmbeddedResource Include="Resources\$(AssemblyName).rd.xml" /> | |||
</ItemGroup> | |||
<ItemGroup> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VS had been adding these automatically. I revert then when I remember to.
@@ -49,6 +49,32 @@ internal static Task<int> ReadAsync(this Stream stream, Memory<byte> destination | |||
} | |||
} | |||
} | |||
|
|||
internal static ValueTask WriteAsync(this Stream stream, ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These extension methods seem really weird to me, but I guess they already exist (at least for Read), so that's okay, I suppose? What exactly is going on here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly is going on here?
We have the ManagedWebSocket.cs implementation that's compiled into multiple assemblies. It's compiled into System.Net.WebSockets.dll, which is part of netcoreapp and can always use all the latest bells and whistles, including the new Read/WriteAsync overloads on Stream. It's also copiled into System.Net.WebSockets.WebSocketProtocol.dll, which is an OOB assembly we release specifically to target netstandard2.0, and in particular so that ASP.NET can use ManagedWebSocket whether targeting netstandard2.0 or netcoreapp. When built in this configuration, ManagedWebSocket can't use the new Stream overloads, so we have two choices: either use ifdef'ing in ManagedWebSocket to have two different implementations for the parts of the code where we use the new functionality, or use partial classes to provide polyfills when building for netstandard. We chose the latter, and that's what these are, implementations of the new APIs, with the same signature, but that aren't actually overloads and are instead just static extensions with the same signature otherwise. The implementation is essentially the same as what the base Stream implementation provides in netcoreapp: try to extract the array if there is one and then just delegate to the array overload, or fall back to renting an array, copying data, and using the array-based overload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that makes sense.
@@ -6337,6 +6337,18 @@ public sealed partial class AsyncStateMachineAttribute : System.Runtime.Compiler | |||
{ | |||
public AsyncStateMachineAttribute(System.Type stateMachineType) : base (default(System.Type)) { } | |||
} | |||
public partial struct AsyncValueTaskMethodBuilder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a stupid question, but why are all these structs partial?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know; the ref generator does it this way. I presume to make it easier to have all of the core code in one file, and then have refs for different platforms that add additional APIs.
I looked at everything except the Channels stuff, and aside from a couple minor issues/questions above, it all looks good to me. I can look at Channels tomorrow if that would be helpful. |
If you have the time, thanks. |
ccfd967
to
76e9044
Compare
@stephentoub did you run the Channels stress test with your changes here? if not, would be good to try it too. |
return ValueTaskSourceStatus.Pending; | ||
|
||
case States.CompletionSet: | ||
case States.Released: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't Released an error? It seems like there's nothing good that can come from accessing the object in this state, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can probably make it one now. Previously when we just had IsCompleted, I had to roll them together based on an implementation detail elsewhere, but I should be able to address this now. I will if I can. I believe that branch is now actually effectively dead code.
ThrowIncorrectCurrentIdException(); | ||
} | ||
|
||
if (!IsCompleted) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will allow GetResult to succeed if we're in the Released state, right? Do we want to do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or does the _currentId check prevent this? If so, should we assert we're not in released state? Maybe we can't reliably do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or does the _currentId check prevent this?
Right.
If so, should we assert we're not in released state?
I try to avoid adding asserts for things that are actually triggerable using public APIs. In this case such an assert would even be triggered by our tests that validate an exception is thrown when GetResult is called again.
|
||
public bool TrySetResult(T result) | ||
{ | ||
if (Interlocked.CompareExchange(ref _state, (int)States.CompletionReserved, (int)States.Owned) == (int)States.Owned) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely clear on why you have the CompletionReserved state. What is this guarding against? I suppose it prevents accidentally overwriting the result if this is called twice, but it seems like you're already in a bad place if TrySetResult is called twice.
Also, why return false here as opposed to throw? What's the user of this supposed to do with a return value of false?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely clear on why you have the CompletionReserved state.
I had a reason, which now escapes me. I'm going to leave it for now, as it's tied in to the whole state machine, but can subsequently look into removing it.
What's the user of this supposed to do with a return value of false?
There can be multiple operations racing to complete one of these if cancellation is involved. The synchronization and bool return is used in coordinating that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a reason, which now escapes me.
Oh, right, it was also because of cancellation. Cancellation races with another completion, the completion is about to store the value, cancellation comes along and completes the operation, the awaiter for that operation wakes up and retrieves the cancellation result, the object goes back to being pooled, another operation comes along and starts using it, and then that original operation writes its result over someone else's object. Before we do any writing, we need to ensure we own the object.
private ExecutionContext _executionContext; | ||
private short _currentId; | ||
|
||
public ValueTask CreateNonGenericValueTask() => new ValueTask(this, _currentId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming on these is a little weird. It confused me when I saw it used, but it's really just a trivial thing -- get the ValueTask for this TaskSource. The equivalent on TCS is just .Task, right? Can we just use .ValueTask here, or .[Non]GenericValueTask?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean just removing the "Create" and making it a property? Sure. Or, maybe I'll do ValueTask and ValueTaskOfT, or something like that.
} | ||
} | ||
|
||
internal abstract class ResettableValueTaskSource<T> : ResettableValueTaskSource, IValueTaskSource, IValueTaskSource<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I prefer seeing different classes for void/T. I find it less confusing to use in practice.
} | ||
} | ||
|
||
internal sealed class ManualResetValueTaskSource<T> : IValueTaskSource<T>, IValueTaskSource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems pretty close to a general purpose ValueTaskSource :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is, but for a specific set of scenarios. It wouldn't work, for example, in a situation where you need a different base type (e.g. SocketAsyncEventArgs), or where you effectively want it auto-reset once the last user is done with it (e.g. as in AsyncOperation), or other such things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you say "need a different base type" I assume you mean "want to avoid an extra allocation by implementing IValueTaskSource on an existing class". Right?
It's nice that we did this with internally in Sockets, but honestly, I question whether it really matters. The whole point of both SAEA and IValueTaskSource is that they should be reused, ideally many, many times. So the allocation cost is amortized and thus insignificant.
That's partly why I would really like to see some sort of general purpose class available. Sure, if the extra-super-optimized case you would need a struct, and delegate the interface impl to it, etc, etc. But in most real world cases, I'm very skeptical that the extra alloc matters -- especially when compared to what users are probably doing today, which is allocating a TaskCompletionSource per operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you mean "want to avoid an extra allocation by implementing IValueTaskSource on an existing class". Right?
Yes.
The whole point of both SAEA and IValueTaskSource is that they should be reused, ideally many, many times.
I would say for IValueTaskSource that they "can" be reused many, many times. In channels, for example, you can have many producers and consumers all accessing a channel concurrently. The most value for the least effort comes from caching a single IValueTaskSource instance and reusing that one, but when multiple producers or multiple consumers are all partying at the same time, only one of each is getting the reusable one, and the rest are all getting throwaways. We could pool more, of course, but the management of that isn't free, and adds complexity. So I do expect these to be used in non-reuse scenarios and I still want them to be as cheap as possible then. I could of course have two different implementations for the two different cases, but that also adds complexity.
That's partly why I would really like to see some sort of general purpose class available.
No one is arguing with that. But even if you take the base task piece away, there are still differences in semantics that leads to needing to figure out which scenarios you care about and what knobs you want to expose. This test ManualResetValueTaskSource wouldn't actually work as the implementation for any of the ones in the product because, well, they all automatically reset rather than manually resetting.
|
||
public void Reset() | ||
{ | ||
_completed = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this throw if it hasn't been consumed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could. This method is never actually called currently :)
A few comments/questions above, but nothing serious. Overall this looks great. |
Yup. Thanks. |
@@ -0,0 +1,83 @@ | |||
// Licensed to the .NET Foundation under one or more agreements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would there be mirroring issues if you include it here while it is already in coreclr? cc @Anipik
Also the path here is Threading/Tasks/IValueTaskSource.cs but in coreclr it was Threading/Tasks/Sources/IValueTaskSource.cs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a stray file and should be deleted. The file has already mirrored to corefx, it's just at a different path; this is an old copy at a different location that somehow snuck in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleted.
76e9044
to
1e3f404
Compare
@@ -857,7 +856,7 @@ public static Task WriteAllBytesAsync(string path, byte[] bytes, CancellationTok | |||
|
|||
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, DefaultBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan)) | |||
{ | |||
await fs.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); | |||
await fs.WriteAsync(new ReadOnlyMemory<byte>(bytes, 0, bytes.Length), cancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use the single arg constructor here instead so it avoids unnecessary checks? Anywhere we have array, 0, array.Length
, we can simply pass array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, will change. I did that in most places, but missed this one.
@@ -801,8 +798,7 @@ public CopyToAsyncStream(DeflateStream deflateStream, Stream destination, int bu | |||
int bytesRead = _deflateStream._inflater.Inflate(_arrayPoolBuffer, 0, _arrayPoolBuffer.Length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the span based overload here to avoid passing in 0, length
.
@@ -238,7 +238,7 @@ public override void WriteByte(byte value) | |||
public override int Read(byte[] buffer, int offset, int count) | |||
{ | |||
CheckReadArguments(buffer, offset, count); | |||
return ReadAsyncCore(buffer, offset, count, default(CancellationToken), useAsync: false).ConfigureAwait(false).GetAwaiter().GetResult(); | |||
return ReadAsyncCore(buffer, offset, count, default(CancellationToken), useAsync: false).GetAwaiter().GetResult(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost everwhere else that had ConfigureAwait(false) kept it. But here you removed it.
Since you understand it way better than I do, I'll trust you if removing it is right, but I wanted to call it out for confirmation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConfigureAwait is only relevant when await is being used, as it influences which awaiter is used and thus how its OnCompleted behaves. Here we're just synchronously blocking waiting for the operation to complete, so ConfigureAwait is a nop.
@@ -410,6 +410,7 @@ | |||
<Reference Include="System.Security.Claims" /> | |||
<Reference Include="System.Security.Principal.Windows" /> | |||
<Reference Include="System.Threading" /> | |||
<Reference Include="System.Threading.ThreadPool" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: sort ordering
/// <summary>Whether exceptions that emerge should be wrapped in IOExceptions.</summary> | ||
internal bool _wrapExceptionsInIOExceptions; | ||
} | ||
|
||
/// <summary>A SocketAsyncEventArgs that can be awaited to get the result of an operation.</summary> | ||
internal sealed class AwaitableSocketAsyncEventArgs : SocketAsyncEventArgs, IValueTaskSource, IValueTaskSource<int> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this class a copy/paste from src/System.Net.Sockets/src/System/Net/Sockets/NetworkStream.cs or are there changes here we should review?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The one from NetworkStream was moved here, then augmented with the interfaces. I removed it from NetworkStream along with its CopyToAsync override, as with these changes and with the base Stream.CopyToAsync being updated to use the new overloads, the overridden CopyToAsync wasn't adding enough additional value to be worth the extra code/complexity.
/// <summary>Gets the result of the completion operation.</summary> | ||
/// <returns>Number of bytes transferred.</returns> | ||
/// <remarks> | ||
/// Unlike Task's awaiter's GetResult, this does not block until the operation completes: it must only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Task's awaiter's GetResult
is difficult to read.
{ | ||
await RunWithConnectedNetworkStreamsAsync(async (server, client) => | ||
{ | ||
byte[] writeBuffer = new byte[10 * 1024 * 1024]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use var
@@ -18,6 +18,7 @@ public UnixDomainSocketTest(ITestOutputHelper output) | |||
_log = TestLogging.GetInstance(); | |||
} | |||
|
|||
[ActiveIssue(27542)] | |||
[OuterLoop] // TODO: Issue #11345 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This issue has been closed, update the comment? https://github.com/dotnet/corefx/issues/11345
Do we need to point it to this one now? https://github.com/dotnet/corefx/issues/14519
cc @davidsh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
11345
There are tons of references to this still around the repo. They should be cleaned up, but I'm not going to do that in this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test should still be left as outerloop.
{ | ||
byte[] buffer = ArrayPool<byte>.Shared.Rent(source.Length); | ||
source.Span.CopyTo(buffer); | ||
return new ValueTask(FinishWriteAsync(stream.WriteAsync(buffer, 0, source.Length, cancellationToken), buffer)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is buffer.Length == source.Length
here?
Why not use the RoM overload of WriteAsync instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As noted above, this code has to also run in a netstandard2.0
assembly (consumed by ASP.NET Core in particular).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is buffer.Length == source.Length here?
Not necessarily. buffer.Length >= source.Length. The ArrayPool could have returned a longer array.
Why not use the RoM overload of WriteAsync instead?
This is the implementation of that "overload". See #27497 (comment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily. buffer.Length >= source.Length. The ArrayPool could have returned a longer array.
Got it.
This is the implementation of that "overload".
Lol, that's right. I didn't connect the dots.
@@ -37,6 +37,7 @@ | |||
<Reference Include="System.Runtime" /> | |||
<Reference Include="System.Runtime.Extensions" /> | |||
<Reference Include="System.Threading" /> | |||
<Reference Include="System.Threading.ThreadPool" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: sort order, here and above.
} | ||
|
||
ExceptionDispatchInfo error = _error; | ||
T result = _result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we store the _result in a local before returning it? Same with error.
Edit: Is it because after the _state changes, the value could change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because once _state is set to Released below, someone else can immediately start using this object, so we need the state captured locally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it because after the _state changes, the value could change?
Right
{ | ||
Task<int> source = Task.Delay(1).ContinueWith(_ => 42); | ||
ValueTask<int> t = new ValueTask<int>(source); | ||
ValueTask<int> t = new ValueTask<int>(ManualResetValueTaskSource.Completed<int>(42, null), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use var
var dsm = new DelegateStateMachine(); | ||
TaskAwaiter<int> t = new TaskCompletionSource<int>().Task.GetAwaiter(); | ||
|
||
Assert.InRange(numAwaits, 1, int.MaxValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the point of this check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is copy-and-paste of the generic one, which is from a while ago. I assume I was thinking I wanted to ensure that the input test data made sense, so that if the test then failed, it wasn't because of bad inputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, modulo nits
1e3f404
to
276d656
Compare
This commit does several things: - Exposes the new `ValueTask` extensibility model being added in coreclr. The ValueTask-related files will separately be mirrored over to corefx to enable the netstandard build of System.Threading.Tasks.Extensions. - Adapts all `Stream`-derived types to return `ValueTask` instead of `Task` from `WriteAsync`. - Changes the new `WebSocket` `SendAsync` method to return `ValueTask` instead of `Task`, and updates the `ManagedWebSocket` implementation accordingly. Most `SendAsync`s on `ManagedWebSocket` should now return a `ValueTask` that's either completed synchronously (no allocation) or using a pooled object. It now uses the underlying transport's new `WriteAsync` overload that returns `ValueTask`. - Switches more uses of `ReadAsync` and `WriteAsync` over to the new overloads, including in Process, DeflateStream, BrotliStream, File, HttpClient, SslStream, WebClient, BufferedStream, CryptoStream, - Removed some unnecessary array clearing from various routines using ArrayPool (after the clearing was added we changed our minds and decided clearing was only necessary in very specific circumstances) - Implements a custom `IValueTaskSource` in Socket, such that async receives and sends become allocation-free (ammortized). `NetworkStream` then inherits this functionality, such that its new `ReadAsync` and `WriteAsync` are also allocation-free (in the unbounded channel implementations; we can subsequently add it in for bounded). - Implements a custom `IValueTaskSource` in System.Threading.Channels, such that reading and writing are ammortized allocation-free up to one concurrent reader and writer. - A few random things I noticed as I was going through, e.g. missing ConfigureAwait, some incorrect synchronization in tests, etc. - Adds a ton of new tests, mainly in System.Threading.Tasks.Extensions, System.Threading.Channels, and System.Net.Sockets.
276d656
to
9152f9f
Compare
Cherry-picked into #27640 (with a few additional fixes added there) |
This commit does several things:
ValueTask
extensibility model being added in coreclr. The ValueTask-related files will separately be mirrored over to corefx to enable the netstandard build of System.Threading.Tasks.Extensions.Stream
-derived types to returnValueTask
instead ofTask
fromWriteAsync
.WebSocket
SendAsync
method to returnValueTask
instead ofTask
, and updates theManagedWebSocket
implementation accordingly. MostSendAsync
s onManagedWebSocket
should now return aValueTask
that's either completed synchronously (no allocation) or using a pooled object. It now uses the underlying transport's newWriteAsync
overload that returnsValueTask
.ReadAsync
andWriteAsync
over to the new overloads, including in Process, DeflateStream, BrotliStream, File, HttpClient, SslStream, WebClient, BufferedStream, CryptoStream,IValueTaskSource
in Socket, such that async receives and sends become allocation-free (ammortized).NetworkStream
then inherits this functionality, such that its newReadAsync
andWriteAsync
are also allocation-free (in the unbounded channel implementations; we can subsequently add it in for bounded).IValueTaskSource
in System.Threading.Channels, such that reading and writing are ammortized allocation-free up to one concurrent reader and writer.ConfigureAwait
, some incorrect synchronization in tests, etc.Fixes https://github.com/dotnet/corefx/issues/27445
Depends on dotnet/coreclr#16618