Add StreamPipeReader (#4182)
This commit is contained in:
parent
e785437288
commit
c06a06d16b
|
|
@ -26,3 +26,4 @@ scripts/tmp/
|
|||
.tools/
|
||||
src/**/global.json
|
||||
launchSettings.json
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
public class NoopStream : Stream
|
||||
{
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => throw new NotImplementedException();
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult<int>(0);
|
||||
}
|
||||
|
||||
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new ValueTask<int>(0);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return default(ValueTask);
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
public class StreamPipeReaderBenchmark
|
||||
{
|
||||
private StreamPipeReader _pipeReaderNoop;
|
||||
private StreamPipeReader _pipeReaderHelloWorld;
|
||||
private StreamPipeReader _pipeReaderHelloWorldAync;
|
||||
|
||||
[IterationSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_pipeReaderNoop = new StreamPipeReader(new NoopStream());
|
||||
_pipeReaderHelloWorld = new StreamPipeReader(new HelloWorldStream());
|
||||
_pipeReaderHelloWorldAync = new StreamPipeReader(new HelloWorldAsyncStream());
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ReadNoop()
|
||||
{
|
||||
await _pipeReaderNoop.ReadAsync();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ReadHelloWorld()
|
||||
{
|
||||
var result = await _pipeReaderHelloWorld.ReadAsync();
|
||||
_pipeReaderHelloWorld.AdvanceTo(result.Buffer.End);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ReadHelloWorldAsync()
|
||||
{
|
||||
var result = await _pipeReaderHelloWorldAync.ReadAsync();
|
||||
_pipeReaderHelloWorldAync.AdvanceTo(result.Buffer.End);
|
||||
}
|
||||
|
||||
private class HelloWorldStream : NoopStream
|
||||
{
|
||||
private static byte[] bytes = Encoding.ASCII.GetBytes("Hello World");
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
bytes.CopyTo(buffer, 0);
|
||||
return Task.FromResult(11);
|
||||
}
|
||||
|
||||
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bytes.CopyTo(buffer);
|
||||
|
||||
return new ValueTask<int>(11);
|
||||
}
|
||||
}
|
||||
|
||||
private class HelloWorldAsyncStream : NoopStream
|
||||
{
|
||||
private static byte[] bytes = Encoding.ASCII.GetBytes("Hello World");
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
bytes.CopyTo(buffer, 0);
|
||||
return 11;
|
||||
}
|
||||
|
||||
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Yield();
|
||||
bytes.CopyTo(buffer);
|
||||
return 11;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
|
|
@ -35,55 +33,5 @@ namespace Microsoft.AspNetCore.Http
|
|||
{
|
||||
await _pipeWriter.WriteAsync(_largeWrite);
|
||||
}
|
||||
|
||||
public class NoopStream : Stream
|
||||
{
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => throw new System.NotImplementedException();
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw new System.NotImplementedException();
|
||||
|
||||
public override long Position { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return default(ValueTask);
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.IO.Pipelines
|
||||
{
|
||||
public sealed class BufferSegment : ReadOnlySequenceSegment<byte>
|
||||
{
|
||||
private IMemoryOwner<byte> _memoryOwner;
|
||||
private BufferSegment _next;
|
||||
private int _end;
|
||||
|
||||
/// <summary>
|
||||
/// The Start represents the offset into AvailableMemory where the range of "active" bytes begins. At the point when the block is leased
|
||||
/// the Start is guaranteed to be equal to 0. The value of Start may be assigned anywhere between 0 and
|
||||
/// AvailableMemory.Length, and must be equal to or less than End.
|
||||
/// </summary>
|
||||
public int Start { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The End represents the offset into AvailableMemory where the range of "active" bytes ends. At the point when the block is leased
|
||||
/// the End is guaranteed to be equal to Start. The value of Start may be assigned anywhere between 0 and
|
||||
/// Buffer.Length, and must be equal to or less than End.
|
||||
/// </summary>
|
||||
public int End
|
||||
{
|
||||
get => _end;
|
||||
set
|
||||
{
|
||||
Debug.Assert(value - Start <= AvailableMemory.Length);
|
||||
|
||||
_end = value;
|
||||
Memory = AvailableMemory.Slice(Start, _end - Start);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the next block of data when the overall "active" bytes spans multiple blocks. At the point when the block is
|
||||
/// leased Next is guaranteed to be null. Start, End, and Next are used together in order to create a linked-list of discontiguous
|
||||
/// working memory. The "active" memory is grown when bytes are copied in, End is increased, and Next is assigned. The "active"
|
||||
/// memory is shrunk when bytes are consumed, Start is increased, and blocks are returned to the pool.
|
||||
/// </summary>
|
||||
public BufferSegment NextSegment
|
||||
{
|
||||
get => _next;
|
||||
set
|
||||
{
|
||||
_next = value;
|
||||
Next = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMemory(IMemoryOwner<byte> memoryOwner)
|
||||
{
|
||||
SetMemory(memoryOwner, 0, 0);
|
||||
}
|
||||
|
||||
public void SetMemory(IMemoryOwner<byte> memoryOwner, int start, int end)
|
||||
{
|
||||
_memoryOwner = memoryOwner;
|
||||
|
||||
AvailableMemory = _memoryOwner.Memory;
|
||||
|
||||
RunningIndex = 0;
|
||||
Start = start;
|
||||
End = end;
|
||||
NextSegment = null;
|
||||
}
|
||||
|
||||
public void ResetMemory()
|
||||
{
|
||||
_memoryOwner.Dispose();
|
||||
_memoryOwner = null;
|
||||
AvailableMemory = default;
|
||||
}
|
||||
|
||||
internal IMemoryOwner<byte> MemoryOwner => _memoryOwner;
|
||||
|
||||
public Memory<byte> AvailableMemory { get; private set; }
|
||||
|
||||
public int Length => End - Start;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of writable bytes in this segment. It is the amount of bytes between Length and End
|
||||
/// </summary>
|
||||
public int WritableBytes
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => AvailableMemory.Length - End;
|
||||
}
|
||||
|
||||
public void SetNext(BufferSegment segment)
|
||||
{
|
||||
Debug.Assert(segment != null);
|
||||
Debug.Assert(Next == null);
|
||||
|
||||
NextSegment = segment;
|
||||
|
||||
segment = this;
|
||||
|
||||
while (segment.Next != null)
|
||||
{
|
||||
segment.NextSegment.RunningIndex = segment.RunningIndex + segment.Length;
|
||||
segment = segment.NextSegment;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,364 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements PipeReader using an underlying stream.
|
||||
/// </summary>
|
||||
public class StreamPipeReader : PipeReader
|
||||
{
|
||||
private readonly int _minimumSegmentSize;
|
||||
private readonly Stream _readingStream;
|
||||
private readonly MemoryPool<byte> _pool;
|
||||
|
||||
private CancellationTokenSource _internalTokenSource;
|
||||
private bool _isCompleted;
|
||||
private ExceptionDispatchInfo _exceptionInfo;
|
||||
|
||||
private BufferSegment _readHead;
|
||||
private int _readIndex;
|
||||
|
||||
private BufferSegment _readTail;
|
||||
private long _bufferedBytes;
|
||||
private bool _examinedEverything;
|
||||
private object _lock = new object();
|
||||
|
||||
private CancellationTokenSource InternalTokenSource
|
||||
{
|
||||
get
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
if (_internalTokenSource == null)
|
||||
{
|
||||
_internalTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
return _internalTokenSource;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
_internalTokenSource = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new StreamPipeReader.
|
||||
/// </summary>
|
||||
/// <param name="readingStream">The stream to read from.</param>
|
||||
public StreamPipeReader(Stream readingStream) : this(readingStream, minimumSegmentSize: 4096)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new StreamPipeReader.
|
||||
/// </summary>
|
||||
/// <param name="readingStream">The stream to read from.</param>
|
||||
/// <param name="minimumSegmentSize">The minimum segment size to return from ReadAsync.</param>
|
||||
/// <param name="pool"></param>
|
||||
public StreamPipeReader(Stream readingStream, int minimumSegmentSize, MemoryPool<byte> pool = null)
|
||||
{
|
||||
_minimumSegmentSize = minimumSegmentSize;
|
||||
_readingStream = readingStream;
|
||||
_pool = pool ?? MemoryPool<byte>.Shared;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AdvanceTo(SequencePosition consumed)
|
||||
{
|
||||
AdvanceTo(consumed, consumed);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
|
||||
{
|
||||
ThrowIfCompleted();
|
||||
|
||||
if (_readHead == null || _readTail == null)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException_NoDataRead();
|
||||
}
|
||||
|
||||
AdvanceTo((BufferSegment)consumed.GetObject(), consumed.GetInteger(), (BufferSegment)examined.GetObject(), examined.GetInteger());
|
||||
}
|
||||
|
||||
private void AdvanceTo(BufferSegment consumedSegment, int consumedIndex, BufferSegment examinedSegment, int examinedIndex)
|
||||
{
|
||||
if (consumedSegment == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var returnStart = _readHead;
|
||||
var returnEnd = consumedSegment;
|
||||
|
||||
var consumedBytes = new ReadOnlySequence<byte>(returnStart, _readIndex, consumedSegment, consumedIndex).Length;
|
||||
|
||||
_bufferedBytes -= consumedBytes;
|
||||
|
||||
Debug.Assert(_bufferedBytes >= 0);
|
||||
|
||||
_examinedEverything = false;
|
||||
|
||||
if (examinedSegment == _readTail)
|
||||
{
|
||||
// If we examined everything, we force ReadAsync to actually read from the underlying stream
|
||||
// instead of returning a ReadResult from TryRead.
|
||||
_examinedEverything = examinedIndex == _readTail.End - _readTail.Start;
|
||||
}
|
||||
|
||||
// Three cases here:
|
||||
// 1. All data is consumed. If so, we reset _readHead and _readTail to _readTail's original memory owner
|
||||
// SetMemory on a IMemoryOwner will reset the internal Memory<byte> to be an empty segment
|
||||
// 2. A segment is entirely consumed but there is still more data in nextSegments
|
||||
// We are allowed to remove an extra segment. by setting returnEnd to be the next block.
|
||||
// 3. We are in the middle of a segment.
|
||||
// Move _readHead and _readIndex to consumedSegment and index
|
||||
if (_bufferedBytes == 0)
|
||||
{
|
||||
_readTail.SetMemory(_readTail.MemoryOwner);
|
||||
_readHead = _readTail;
|
||||
returnEnd = _readTail;
|
||||
_readIndex = 0;
|
||||
}
|
||||
else if (consumedIndex == returnEnd.Length)
|
||||
{
|
||||
var nextBlock = returnEnd.NextSegment;
|
||||
_readHead = nextBlock;
|
||||
_readIndex = 0;
|
||||
returnEnd = nextBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
_readHead = consumedSegment;
|
||||
_readIndex = consumedIndex;
|
||||
}
|
||||
|
||||
// Remove all blocks that are freed (except the last one)
|
||||
while (returnStart != returnEnd)
|
||||
{
|
||||
returnStart.ResetMemory();
|
||||
returnStart = returnStart.NextSegment;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void CancelPendingRead()
|
||||
{
|
||||
InternalTokenSource.Cancel();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Complete(Exception exception = null)
|
||||
{
|
||||
if (_isCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isCompleted = true;
|
||||
if (exception != null)
|
||||
{
|
||||
_exceptionInfo = ExceptionDispatchInfo.Capture(exception);
|
||||
}
|
||||
|
||||
var segment = _readHead;
|
||||
while (segment != null)
|
||||
{
|
||||
segment.ResetMemory();
|
||||
segment = segment.NextSegment;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnWriterCompleted(Action<Exception, object> callback, object state)
|
||||
{
|
||||
throw new NotSupportedException("OnWriterCompleted is not supported");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO ReadyAsync needs to throw if there are overlapping reads.
|
||||
ThrowIfCompleted();
|
||||
|
||||
// PERF: store InternalTokenSource locally to avoid querying it twice (which acquires a lock)
|
||||
var tokenSource = InternalTokenSource;
|
||||
if (TryReadInternal(tokenSource, out var readResult))
|
||||
{
|
||||
return readResult;
|
||||
}
|
||||
|
||||
var reg = new CancellationTokenRegistration();
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
reg = cancellationToken.Register(state => ((StreamPipeReader)state).Cancel(), this);
|
||||
}
|
||||
|
||||
using (reg)
|
||||
{
|
||||
var isCanceled = false;
|
||||
try
|
||||
{
|
||||
AllocateReadTail();
|
||||
#if NETCOREAPP2_2
|
||||
var length = await _readingStream.ReadAsync(_readTail.AvailableMemory.Slice(_readTail.End), tokenSource.Token);
|
||||
#elif NETSTANDARD2_0
|
||||
if (!MemoryMarshal.TryGetArray<byte>(_readTail.AvailableMemory.Slice(_readTail.End), out var arraySegment))
|
||||
{
|
||||
ThrowHelper.CreateInvalidOperationException_NoArrayFromMemory();
|
||||
}
|
||||
|
||||
var length = await _readingStream.ReadAsync(arraySegment.Array, arraySegment.Offset, arraySegment.Count, tokenSource.Token);
|
||||
#else
|
||||
#error Target frameworks need to be updated.
|
||||
#endif
|
||||
Debug.Assert(length + _readTail.End <= _readTail.AvailableMemory.Length);
|
||||
|
||||
_readTail.End += length;
|
||||
_bufferedBytes += length;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
ClearCancellationToken();
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
isCanceled = true;
|
||||
}
|
||||
|
||||
return new ReadResult(GetCurrentReadOnlySequence(), isCanceled, IsCompletedOrThrow());
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearCancellationToken()
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
_internalTokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfCompleted()
|
||||
{
|
||||
if (_isCompleted)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool TryRead(out ReadResult result)
|
||||
{
|
||||
ThrowIfCompleted();
|
||||
|
||||
return TryReadInternal(InternalTokenSource, out result);
|
||||
}
|
||||
|
||||
private bool TryReadInternal(CancellationTokenSource source, out ReadResult result)
|
||||
{
|
||||
var isCancellationRequested = source.IsCancellationRequested;
|
||||
if (isCancellationRequested || _bufferedBytes > 0 && !_examinedEverything)
|
||||
{
|
||||
// If TryRead/ReadAsync are called and cancellation is requested, we need to make sure memory is allocated for the ReadResult,
|
||||
// otherwise if someone calls advance afterward on the ReadResult, it will throw.
|
||||
if (isCancellationRequested)
|
||||
{
|
||||
AllocateReadTail();
|
||||
|
||||
ClearCancellationToken();
|
||||
}
|
||||
|
||||
result = new ReadResult(
|
||||
GetCurrentReadOnlySequence(),
|
||||
isCanceled: isCancellationRequested,
|
||||
IsCompletedOrThrow());
|
||||
return true;
|
||||
}
|
||||
|
||||
result = new ReadResult();
|
||||
return false;
|
||||
}
|
||||
|
||||
private ReadOnlySequence<byte> GetCurrentReadOnlySequence()
|
||||
{
|
||||
return new ReadOnlySequence<byte>(_readHead, _readIndex, _readTail, _readTail.End - _readTail.Start);
|
||||
}
|
||||
|
||||
private void AllocateReadTail()
|
||||
{
|
||||
if (_readHead == null)
|
||||
{
|
||||
Debug.Assert(_readTail == null);
|
||||
_readHead = CreateBufferSegment();
|
||||
_readHead.SetMemory(_pool.Rent(GetSegmentSize()));
|
||||
_readTail = _readHead;
|
||||
}
|
||||
else if (_readTail.WritableBytes == 0)
|
||||
{
|
||||
CreateNewTailSegment();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateNewTailSegment()
|
||||
{
|
||||
var nextSegment = CreateBufferSegment();
|
||||
nextSegment.SetMemory(_pool.Rent(GetSegmentSize()));
|
||||
_readTail.SetNext(nextSegment);
|
||||
_readTail = nextSegment;
|
||||
}
|
||||
|
||||
private int GetSegmentSize() => Math.Min(_pool.MaxBufferSize, _minimumSegmentSize);
|
||||
|
||||
private BufferSegment CreateBufferSegment()
|
||||
{
|
||||
// TODO this can pool buffer segment objects
|
||||
return new BufferSegment();
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
InternalTokenSource.Cancel();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsCompletedOrThrow()
|
||||
{
|
||||
if (!_isCompleted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_exceptionInfo != null)
|
||||
{
|
||||
ThrowLatchedException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ThrowLatchedException()
|
||||
{
|
||||
_exceptionInfo.Throw();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
public static void ThrowInvalidOperationException_NoReadingAllowed() => throw CreateInvalidOperationException_NoReadingAllowed();
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static Exception CreateInvalidOperationException_NoReadingAllowed() => new InvalidOperationException("Reading is not allowed after reader was completed.");
|
||||
|
||||
public static void ThrowInvalidOperationException_NoArrayFromMemory() => throw CreateInvalidOperationException_NoArrayFromMemory();
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static Exception CreateInvalidOperationException_NoArrayFromMemory() => new InvalidOperationException("Could not get byte[] from Memory.");
|
||||
|
||||
public static void ThrowInvalidOperationException_NoDataRead() => throw CreateInvalidOperationException_NoDataRead();
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static Exception CreateInvalidOperationException_NoDataRead() => new InvalidOperationException("No data has been read into the StreamPipeReader.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||
<TargetFrameworks>net461</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -11,19 +11,25 @@ namespace Microsoft.AspNetCore.Http.Tests
|
|||
{
|
||||
protected const int MaximumSizeHigh = 65;
|
||||
|
||||
protected const int MinimumSegmentSize = 4096;
|
||||
|
||||
public MemoryStream MemoryStream { get; set; }
|
||||
|
||||
public PipeWriter Writer { get; set; }
|
||||
|
||||
public PipeReader Reader { get; set; }
|
||||
|
||||
protected PipeTest()
|
||||
{
|
||||
MemoryStream = new MemoryStream();
|
||||
Writer = new StreamPipeWriter(MemoryStream, 4096, new TestMemoryPool());
|
||||
Writer = new StreamPipeWriter(MemoryStream, MinimumSegmentSize, new TestMemoryPool());
|
||||
Reader = new StreamPipeReader(MemoryStream, MinimumSegmentSize, new TestMemoryPool());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Writer.Complete();
|
||||
Reader.Complete();
|
||||
}
|
||||
|
||||
public byte[] Read()
|
||||
|
|
@ -32,6 +38,17 @@ namespace Microsoft.AspNetCore.Http.Tests
|
|||
return ReadWithoutFlush();
|
||||
}
|
||||
|
||||
public void Write(byte[] data)
|
||||
{
|
||||
MemoryStream.Write(data, 0, data.Length);
|
||||
MemoryStream.Position = 0;
|
||||
}
|
||||
|
||||
public void WriteWithoutPosition(byte[] data)
|
||||
{
|
||||
MemoryStream.Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
public byte[] ReadWithoutFlush()
|
||||
{
|
||||
MemoryStream.Position = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Http.Tests
|
|||
{
|
||||
var span = Writer.GetSpan(0);
|
||||
|
||||
var secondSpan = Writer.GetSpan(8000);
|
||||
var secondSpan = Writer.GetSpan(10000);
|
||||
|
||||
Assert.False(span.SequenceEqual(secondSpan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO.Pipelines;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Tests
|
||||
{
|
||||
public class ReadAsyncCancellationTests : PipeTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task AdvanceShouldResetStateIfReadCanceled()
|
||||
{
|
||||
Reader.CancelPendingRead();
|
||||
|
||||
var result = await Reader.ReadAsync();
|
||||
var buffer = result.Buffer;
|
||||
Reader.AdvanceTo(buffer.End);
|
||||
|
||||
Assert.False(result.IsCompleted);
|
||||
Assert.True(result.IsCanceled);
|
||||
Assert.True(buffer.IsEmpty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancellingBeforeAdvance()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes("Hello World"));
|
||||
|
||||
var result = await Reader.ReadAsync();
|
||||
var buffer = result.Buffer;
|
||||
|
||||
Assert.Equal(11, buffer.Length);
|
||||
Assert.False(result.IsCanceled);
|
||||
Assert.True(buffer.IsSingleSegment);
|
||||
var array = new byte[11];
|
||||
buffer.First.Span.CopyTo(array);
|
||||
Assert.Equal("Hello World", Encoding.ASCII.GetString(array));
|
||||
|
||||
Reader.CancelPendingRead();
|
||||
|
||||
Reader.AdvanceTo(buffer.End);
|
||||
|
||||
var awaitable = Reader.ReadAsync();
|
||||
|
||||
Assert.True(awaitable.IsCompleted);
|
||||
|
||||
result = await awaitable;
|
||||
|
||||
Assert.True(result.IsCanceled);
|
||||
|
||||
Reader.AdvanceTo(result.Buffer.Start, result.Buffer.Start);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsyncWithNewCancellationTokenNotAffectedByPrevious()
|
||||
{
|
||||
Write(new byte[1]);
|
||||
|
||||
var cancellationTokenSource1 = new CancellationTokenSource();
|
||||
var result = await Reader.ReadAsync(cancellationTokenSource1.Token);
|
||||
Reader.AdvanceTo(result.Buffer.Start);
|
||||
|
||||
cancellationTokenSource1.Cancel();
|
||||
var cancellationTokenSource2 = new CancellationTokenSource();
|
||||
|
||||
// Verifying that ReadAsync does not throw
|
||||
result = await Reader.ReadAsync(cancellationTokenSource2.Token);
|
||||
Reader.AdvanceTo(result.Buffer.Start);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancellingPendingReadBeforeReadAsync()
|
||||
{
|
||||
Reader.CancelPendingRead();
|
||||
|
||||
ReadResult result = await Reader.ReadAsync();
|
||||
ReadOnlySequence<byte> buffer = result.Buffer;
|
||||
Reader.AdvanceTo(buffer.End);
|
||||
|
||||
Assert.False(result.IsCompleted);
|
||||
Assert.True(result.IsCanceled);
|
||||
Assert.True(buffer.IsEmpty);
|
||||
|
||||
byte[] bytes = Encoding.ASCII.GetBytes("Hello World");
|
||||
Write(bytes);
|
||||
|
||||
result = await Reader.ReadAsync();
|
||||
buffer = result.Buffer;
|
||||
|
||||
Assert.Equal(11, buffer.Length);
|
||||
Assert.False(result.IsCanceled);
|
||||
Assert.True(buffer.IsSingleSegment);
|
||||
var array = new byte[11];
|
||||
buffer.First.Span.CopyTo(array);
|
||||
Assert.Equal("Hello World", Encoding.ASCII.GetString(array));
|
||||
|
||||
Reader.AdvanceTo(buffer.Start, buffer.Start);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadAsyncCompletedAfterPreCancellation()
|
||||
{
|
||||
Reader.CancelPendingRead();
|
||||
Write(new byte[] { 1, 2, 3 });
|
||||
|
||||
ValueTaskAwaiter<ReadResult> awaitable = Reader.ReadAsync().GetAwaiter();
|
||||
|
||||
Assert.True(awaitable.IsCompleted);
|
||||
|
||||
ReadResult result = awaitable.GetResult();
|
||||
|
||||
Assert.True(result.IsCanceled);
|
||||
|
||||
awaitable = Reader.ReadAsync().GetAwaiter();
|
||||
|
||||
Assert.True(awaitable.IsCompleted);
|
||||
|
||||
Reader.AdvanceTo(awaitable.GetResult().Buffer.End);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,503 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Tests
|
||||
{
|
||||
public partial class StreamPipeReaderTests : PipeTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task CanRead()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes("Hello World"));
|
||||
var readResult = await Reader.ReadAsync();
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
Assert.Equal(11, buffer.Length);
|
||||
Assert.True(buffer.IsSingleSegment);
|
||||
var array = new byte[11];
|
||||
buffer.First.Span.CopyTo(array);
|
||||
Assert.Equal("Hello World", Encoding.ASCII.GetString(array));
|
||||
Reader.AdvanceTo(buffer.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadMultipleTimes()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
var readResult = await Reader.ReadAsync();
|
||||
|
||||
Assert.Equal(MinimumSegmentSize, readResult.Buffer.Length);
|
||||
Assert.True(readResult.Buffer.IsSingleSegment);
|
||||
|
||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
||||
|
||||
readResult = await Reader.ReadAsync();
|
||||
Assert.Equal(MinimumSegmentSize * 2, readResult.Buffer.Length);
|
||||
Assert.False(readResult.Buffer.IsSingleSegment);
|
||||
|
||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
||||
|
||||
readResult = await Reader.ReadAsync();
|
||||
Assert.Equal(10000, readResult.Buffer.Length);
|
||||
Assert.False(readResult.Buffer.IsSingleSegment);
|
||||
|
||||
Reader.AdvanceTo(readResult.Buffer.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadWithAdvance()
|
||||
{
|
||||
Write(new byte[10000]);
|
||||
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.End);
|
||||
|
||||
readResult = await Reader.ReadAsync();
|
||||
Assert.Equal(MinimumSegmentSize, readResult.Buffer.Length);
|
||||
Assert.True(readResult.Buffer.IsSingleSegment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadWithAdvanceDifferentSegmentSize()
|
||||
{
|
||||
Reader = new StreamPipeReader(MemoryStream, 4095, new TestMemoryPool());
|
||||
Write(new byte[10000]);
|
||||
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.End);
|
||||
|
||||
readResult = await Reader.ReadAsync();
|
||||
Assert.Equal(4095, readResult.Buffer.Length);
|
||||
Assert.True(readResult.Buffer.IsSingleSegment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadWithAdvanceSmallSegments()
|
||||
{
|
||||
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
|
||||
Write(new byte[128]);
|
||||
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.End);
|
||||
|
||||
readResult = await Reader.ReadAsync();
|
||||
Assert.Equal(16, readResult.Buffer.Length);
|
||||
Assert.True(readResult.Buffer.IsSingleSegment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadConsumePartialReadAsyncCallsTryRead()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.GetPosition(2048));
|
||||
|
||||
// Confirm readResults are the same.
|
||||
var readResult2 = await Reader.ReadAsync();
|
||||
|
||||
var didRead = Reader.TryRead(out var readResult3);
|
||||
|
||||
Assert.Equal(readResult2, readResult3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadConsumeEntireTryReadReturnsNothing()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.End);
|
||||
var didRead = Reader.TryRead(out readResult);
|
||||
|
||||
Assert.False(didRead);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadExaminePartialReadAsyncDoesNotReturnMoreBytes()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.GetPosition(2048));
|
||||
|
||||
var readResult2 = await Reader.ReadAsync();
|
||||
|
||||
Assert.Equal(readResult, readResult2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadExamineEntireReadAsyncReturnsNewData()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
||||
|
||||
var readResult2 = await Reader.ReadAsync();
|
||||
Assert.NotEqual(readResult, readResult2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCanBeCancelledViaProvidedCancellationToken()
|
||||
{
|
||||
var pipeReader = new StreamPipeReader(new HangingStream());
|
||||
var cts = new CancellationTokenSource(1);
|
||||
await Task.Delay(1);
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await pipeReader.ReadAsync(cts.Token));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCanBeCanceledViaCancelPendingReadWhenReadIsAsync()
|
||||
{
|
||||
var pipeReader = new StreamPipeReader(new HangingStream());
|
||||
|
||||
var result = new ReadResult();
|
||||
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
var writingTask = pipeReader.ReadAsync();
|
||||
tcs.SetResult(0);
|
||||
result = await writingTask;
|
||||
});
|
||||
await tcs.Task;
|
||||
pipeReader.CancelPendingRead();
|
||||
await task;
|
||||
|
||||
Assert.True(result.IsCanceled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsyncReturnsCanceledIfCanceledBeforeRead()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
|
||||
// Make sure state isn't used from before
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
Reader.CancelPendingRead();
|
||||
var readResultTask = Reader.ReadAsync();
|
||||
Assert.True(readResultTask.IsCompleted);
|
||||
var readResult = readResultTask.GetAwaiter().GetResult();
|
||||
Assert.True(readResult.IsCanceled);
|
||||
readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.End);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadAsyncReturnsCanceledInterleaved()
|
||||
{
|
||||
// Cancel and Read interleaved to confirm cancellations are independent
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
Reader.CancelPendingRead();
|
||||
var readResultTask = Reader.ReadAsync();
|
||||
Assert.True(readResultTask.IsCompleted);
|
||||
var readResult = readResultTask.GetAwaiter().GetResult();
|
||||
Assert.True(readResult.IsCanceled);
|
||||
|
||||
readResult = await Reader.ReadAsync();
|
||||
Assert.False(readResult.IsCanceled);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdvanceWithEmptySequencePositionNoop()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.Start);
|
||||
var readResult2 = await Reader.ReadAsync();
|
||||
|
||||
Assert.Equal(readResult, readResult2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdvanceToInvalidCursorThrows()
|
||||
{
|
||||
Write(new byte[100]);
|
||||
|
||||
var result = await Reader.ReadAsync();
|
||||
var buffer = result.Buffer;
|
||||
|
||||
Reader.AdvanceTo(buffer.End);
|
||||
|
||||
Reader.CancelPendingRead();
|
||||
result = await Reader.ReadAsync();
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => Reader.AdvanceTo(buffer.End));
|
||||
Reader.AdvanceTo(result.Buffer.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdvanceWithoutReadingWithValidSequencePosition()
|
||||
{
|
||||
var sequencePosition = new SequencePosition(new BufferSegment(), 5);
|
||||
Assert.Throws<InvalidOperationException>(() => Reader.AdvanceTo(sequencePosition));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdvanceMultipleSegments()
|
||||
{
|
||||
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
|
||||
Write(new byte[128]);
|
||||
|
||||
var result = await Reader.ReadAsync();
|
||||
Assert.Equal(16, result.Buffer.Length);
|
||||
Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End);
|
||||
|
||||
var result2 = await Reader.ReadAsync();
|
||||
Assert.Equal(32, result2.Buffer.Length);
|
||||
Reader.AdvanceTo(result.Buffer.End, result2.Buffer.End);
|
||||
|
||||
var result3 = await Reader.ReadAsync();
|
||||
Assert.Equal(32, result3.Buffer.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdvanceMultipleSegmentsEdgeCase()
|
||||
{
|
||||
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
|
||||
Write(new byte[128]);
|
||||
|
||||
var result = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End);
|
||||
result = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End);
|
||||
|
||||
var result2 = await Reader.ReadAsync();
|
||||
Assert.Equal(48, result2.Buffer.Length);
|
||||
Reader.AdvanceTo(result.Buffer.End, result2.Buffer.End);
|
||||
|
||||
var result3 = await Reader.ReadAsync();
|
||||
Assert.Equal(32, result3.Buffer.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompleteReaderWithoutAdvanceDoesNotThrow()
|
||||
{
|
||||
Write(new byte[100]);
|
||||
await Reader.ReadAsync();
|
||||
Reader.Complete();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdvanceAfterCompleteThrows()
|
||||
{
|
||||
Write(new byte[100]);
|
||||
var buffer = (await Reader.ReadAsync()).Buffer;
|
||||
|
||||
Reader.Complete();
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => Reader.AdvanceTo(buffer.End));
|
||||
Assert.Equal("Reading is not allowed after reader was completed.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadBetweenBlocks()
|
||||
{
|
||||
var blockSize = 16;
|
||||
Reader = new StreamPipeReader(MemoryStream, blockSize, new TestMemoryPool());
|
||||
|
||||
WriteWithoutPosition(Enumerable.Repeat((byte)'a', blockSize - 5).ToArray());
|
||||
Write(Encoding.ASCII.GetBytes("Hello World"));
|
||||
|
||||
// ReadAsync will only return one chunk at a time, so Advance/ReadAsync to get two chunks
|
||||
var result = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End);
|
||||
result = await Reader.ReadAsync();
|
||||
|
||||
var buffer = result.Buffer;
|
||||
Assert.False(buffer.IsSingleSegment);
|
||||
var helloBuffer = buffer.Slice(blockSize - 5);
|
||||
Assert.False(helloBuffer.IsSingleSegment);
|
||||
var memory = new List<ReadOnlyMemory<byte>>();
|
||||
foreach (var m in helloBuffer)
|
||||
{
|
||||
memory.Add(m);
|
||||
}
|
||||
|
||||
var spans = memory;
|
||||
Reader.AdvanceTo(buffer.Start, buffer.Start);
|
||||
|
||||
Assert.Equal(2, memory.Count);
|
||||
var helloBytes = new byte[spans[0].Length];
|
||||
spans[0].Span.CopyTo(helloBytes);
|
||||
var worldBytes = new byte[spans[1].Length];
|
||||
spans[1].Span.CopyTo(worldBytes);
|
||||
Assert.Equal("Hello", Encoding.ASCII.GetString(helloBytes));
|
||||
Assert.Equal(" World", Encoding.ASCII.GetString(worldBytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsOnReadAfterCompleteReader()
|
||||
{
|
||||
Reader.Complete();
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await Reader.ReadAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReadAfterCancelPendingReadReturnsTrue()
|
||||
{
|
||||
Reader.CancelPendingRead();
|
||||
|
||||
var gotData = Reader.TryRead(out var result);
|
||||
|
||||
Assert.True(result.IsCanceled);
|
||||
|
||||
Reader.AdvanceTo(result.Buffer.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadAsyncWithDataReadyReturnsTaskWithValue()
|
||||
{
|
||||
Write(new byte[20]);
|
||||
var task = Reader.ReadAsync();
|
||||
Assert.True(IsTaskWithResult(task));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelledReadAsyncReturnsTaskWithValue()
|
||||
{
|
||||
Reader.CancelPendingRead();
|
||||
var task = Reader.ReadAsync();
|
||||
Assert.True(IsTaskWithResult(task));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdvancePastMinReadSizeReadAsyncReturnsMoreData()
|
||||
{
|
||||
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
|
||||
Write(new byte[32]);
|
||||
var result = await Reader.ReadAsync();
|
||||
Assert.Equal(16, result.Buffer.Length);
|
||||
|
||||
Reader.AdvanceTo(result.Buffer.GetPosition(12), result.Buffer.End);
|
||||
result = await Reader.ReadAsync();
|
||||
Assert.Equal(20, result.Buffer.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExamineEverythingResetsAfterSuccessfulRead()
|
||||
{
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
||||
|
||||
var readResult2 = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult2.Buffer.GetPosition(2000));
|
||||
|
||||
var readResult3 = await Reader.ReadAsync();
|
||||
Assert.Equal(6192, readResult3.Buffer.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadMultipleTimesAdvanceFreesAppropriately()
|
||||
{
|
||||
var blockSize = 16;
|
||||
var pool = new TestMemoryPool();
|
||||
Reader = new StreamPipeReader(MemoryStream, blockSize, pool);
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
|
||||
for (var i = 0; i < 99; i++)
|
||||
{
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
||||
}
|
||||
|
||||
var result = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(result.Buffer.End);
|
||||
Assert.Equal(1, pool.GetRentCount());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncReadWorks()
|
||||
{
|
||||
MemoryStream = new AsyncStream();
|
||||
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 10000)));
|
||||
|
||||
for (var i = 0; i < 99; i++)
|
||||
{
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
||||
}
|
||||
|
||||
var result = await Reader.ReadAsync();
|
||||
Assert.Equal(1600, result.Buffer.Length);
|
||||
Reader.AdvanceTo(result.Buffer.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConsumePartialBufferWorks()
|
||||
{
|
||||
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 8)));
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.GetPosition(4), readResult.Buffer.End);
|
||||
MemoryStream.Position = 0;
|
||||
|
||||
readResult = await Reader.ReadAsync();
|
||||
var resultString = Encoding.ASCII.GetString(readResult.Buffer.ToArray());
|
||||
Assert.Equal(new string('a', 12), resultString);
|
||||
Reader.AdvanceTo(readResult.Buffer.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConsumePartialBufferBetweenMultipleSegmentsWorks()
|
||||
{
|
||||
Reader = new StreamPipeReader(MemoryStream, 16, new TestMemoryPool());
|
||||
Write(Encoding.ASCII.GetBytes(new string('a', 8)));
|
||||
var readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.GetPosition(4), readResult.Buffer.End);
|
||||
MemoryStream.Position = 0;
|
||||
|
||||
readResult = await Reader.ReadAsync();
|
||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
||||
MemoryStream.Position = 0;
|
||||
|
||||
readResult = await Reader.ReadAsync();
|
||||
var resultString = Encoding.ASCII.GetString(readResult.Buffer.ToArray());
|
||||
Assert.Equal(new string('a', 20), resultString);
|
||||
|
||||
Reader.AdvanceTo(readResult.Buffer.End);
|
||||
}
|
||||
|
||||
private bool IsTaskWithResult<T>(ValueTask<T> task)
|
||||
{
|
||||
return task == new ValueTask<T>(task.Result);
|
||||
}
|
||||
|
||||
private class AsyncStream : MemoryStream
|
||||
{
|
||||
private static byte[] bytes = Encoding.ASCII.GetBytes("Hello World");
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
return await base.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_2
|
||||
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Yield();
|
||||
return await base.ReadAsync(buffer, cancellationToken);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -17,7 +17,6 @@ namespace Microsoft.AspNetCore.Http.Tests
|
|||
[Fact]
|
||||
public async Task CanWriteAsyncMultipleTimesIntoSameBlock()
|
||||
{
|
||||
|
||||
await Writer.WriteAsync(new byte[] { 1 });
|
||||
await Writer.WriteAsync(new byte[] { 2 });
|
||||
await Writer.WriteAsync(new byte[] { 3 });
|
||||
|
|
@ -313,6 +312,13 @@ namespace Microsoft.AspNetCore.Http.Tests
|
|||
await Task.Delay(30000, cancellationToken);
|
||||
return 0;
|
||||
}
|
||||
#if NETCOREAPP2_2
|
||||
public override async ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Delay(30000, cancellationToken);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal class SingleWriteStream : MemoryStream
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
|
|
@ -13,16 +13,27 @@ namespace Microsoft.AspNetCore.Http.Tests
|
|||
{
|
||||
public class TestMemoryPool : MemoryPool<byte>
|
||||
{
|
||||
private MemoryPool<byte> _pool = Shared;
|
||||
private MemoryPool<byte> _pool;
|
||||
|
||||
private bool _disposed;
|
||||
private int _rentCount;
|
||||
public TestMemoryPool()
|
||||
{
|
||||
_pool = new CustomMemoryPool<byte>();
|
||||
}
|
||||
|
||||
public override IMemoryOwner<byte> Rent(int minBufferSize = -1)
|
||||
{
|
||||
CheckDisposed();
|
||||
_rentCount++;
|
||||
return new PooledMemory(_pool.Rent(minBufferSize), this);
|
||||
}
|
||||
|
||||
public int GetRentCount()
|
||||
{
|
||||
return _rentCount;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
|
@ -65,6 +76,7 @@ namespace Microsoft.AspNetCore.Http.Tests
|
|||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_pool._rentCount--;
|
||||
_pool.CheckDisposed();
|
||||
}
|
||||
|
||||
|
|
@ -135,5 +147,58 @@ namespace Microsoft.AspNetCore.Http.Tests
|
|||
return _owner.Memory.Span;
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomMemoryPool<T> : MemoryPool<T>
|
||||
{
|
||||
public override int MaxBufferSize => int.MaxValue;
|
||||
|
||||
public override IMemoryOwner<T> Rent(int minimumBufferSize = -1)
|
||||
{
|
||||
if (minimumBufferSize == -1)
|
||||
{
|
||||
minimumBufferSize = 4096;
|
||||
}
|
||||
|
||||
return new ArrayMemoryPoolBuffer(minimumBufferSize);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private sealed class ArrayMemoryPoolBuffer : IMemoryOwner<T>
|
||||
{
|
||||
private T[] _array;
|
||||
|
||||
public ArrayMemoryPoolBuffer(int size)
|
||||
{
|
||||
_array = new T[size];
|
||||
}
|
||||
|
||||
public Memory<T> Memory
|
||||
{
|
||||
get
|
||||
{
|
||||
T[] array = _array;
|
||||
if (array == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(array));
|
||||
}
|
||||
|
||||
return new Memory<T>(array);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
T[] array = _array;
|
||||
if (array != null)
|
||||
{
|
||||
_array = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,59 +5,61 @@ VisualStudioVersion = 15.0.26124.0
|
|||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication.Abstractions", "Authentication.Abstractions", "{587C3D55-6092-4B86-99F5-E9772C9C1ADB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Authentication.Abstractions", "Authentication.Abstractions\src\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "{565B7B00-96A1-49B8-9753-9E045C6527A2}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Abstractions", "Authentication.Abstractions\src\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "{565B7B00-96A1-49B8-9753-9E045C6527A2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication.Core", "Authentication.Core", "{B51F45A6-428F-40F4-897F-7C62C29EC39A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Authentication.Core", "Authentication.Core\src\Microsoft.AspNetCore.Authentication.Core.csproj", "{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Core", "Authentication.Core\src\Microsoft.AspNetCore.Authentication.Core.csproj", "{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Authentication.Core.Test", "Authentication.Core\test\Microsoft.AspNetCore.Authentication.Core.Test.csproj", "{21071749-4361-4CD0-B5ED-541C72326800}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Core.Test", "Authentication.Core\test\Microsoft.AspNetCore.Authentication.Core.Test.csproj", "{21071749-4361-4CD0-B5ED-541C72326800}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headers", "Headers", "{FF334B62-1AE2-477C-B91B-B28F898DFC3A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.Http.Headers", "Headers\src\Microsoft.Net.Http.Headers.csproj", "{D2B2E73E-A3A4-4996-906C-6647CD7D2634}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Net.Http.Headers", "Headers\src\Microsoft.Net.Http.Headers.csproj", "{D2B2E73E-A3A4-4996-906C-6647CD7D2634}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.Http.Headers.Tests", "Headers\test\Microsoft.Net.Http.Headers.Tests.csproj", "{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Net.Http.Headers.Tests", "Headers\test\Microsoft.Net.Http.Headers.Tests.csproj", "{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http", "Http", "{FB2DCA0F-EB9E-425B-ABBC-D543DBEC090F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http", "Http\src\Microsoft.AspNetCore.Http.csproj", "{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http", "Http\src\Microsoft.AspNetCore.Http.csproj", "{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Tests", "Http\test\Microsoft.AspNetCore.Http.Tests.csproj", "{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Tests", "Http\test\Microsoft.AspNetCore.Http.Tests.csproj", "{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Abstractions", "Http.Abstractions", "{28F3D5CC-1F8E-4E15-94C8-E432DFA0A702}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Abstractions", "Http.Abstractions\src\Microsoft.AspNetCore.Http.Abstractions.csproj", "{D079CD1C-A18F-4457-91BC-432577D2FD37}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Abstractions", "Http.Abstractions\src\Microsoft.AspNetCore.Http.Abstractions.csproj", "{D079CD1C-A18F-4457-91BC-432577D2FD37}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Abstractions.Tests", "Http.Abstractions\test\Microsoft.AspNetCore.Http.Abstractions.Tests.csproj", "{C28045AC-FF16-468C-A1E8-EC192DA2EF19}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Abstractions.Tests", "Http.Abstractions\test\Microsoft.AspNetCore.Http.Abstractions.Tests.csproj", "{C28045AC-FF16-468C-A1E8-EC192DA2EF19}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Extensions", "Http.Extensions", "{CCC61332-7D63-4DDB-B604-884670157624}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Extensions", "Http.Extensions\src\Microsoft.AspNetCore.Http.Extensions.csproj", "{C06F2A33-B887-46BB-8F51-2666EDBE5D38}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Extensions", "Http.Extensions\src\Microsoft.AspNetCore.Http.Extensions.csproj", "{C06F2A33-B887-46BB-8F51-2666EDBE5D38}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Extensions.Tests", "Http.Extensions\test\Microsoft.AspNetCore.Http.Extensions.Tests.csproj", "{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Extensions.Tests", "Http.Extensions\test\Microsoft.AspNetCore.Http.Extensions.Tests.csproj", "{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Features", "Http.Features", "{0B1B3E58-DA37-46D6-B791-47739EF27790}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Features", "Http.Features\src\Microsoft.AspNetCore.Http.Features.csproj", "{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Features", "Http.Features\src\Microsoft.AspNetCore.Http.Features.csproj", "{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Features.Tests", "Http.Features\test\Microsoft.AspNetCore.Http.Features.Tests.csproj", "{5A64C915-7045-4100-B2CB-3A50BD854D2D}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Features.Tests", "Http.Features\test\Microsoft.AspNetCore.Http.Features.Tests.csproj", "{5A64C915-7045-4100-B2CB-3A50BD854D2D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Owin", "Owin", "{4D5C4F16-5DC5-4244-A10F-08545126F61B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Owin", "Owin\src\Microsoft.AspNetCore.Owin.csproj", "{21624719-422E-4621-A17A-C6F10436F1FE}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Owin", "Owin\src\Microsoft.AspNetCore.Owin.csproj", "{21624719-422E-4621-A17A-C6F10436F1FE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Owin.Tests", "Owin\test\Microsoft.AspNetCore.Owin.Tests.csproj", "{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Owin.Tests", "Owin\test\Microsoft.AspNetCore.Owin.Tests.csproj", "{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{391FBA36-BEEB-411A-A588-3F83901C0C1A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "samples\SampleApp\SampleApp.csproj", "{2378049E-ABE9-4843-AAC7-A6C9E704463D}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp", "samples\SampleApp\SampleApp.csproj", "{2378049E-ABE9-4843-AAC7-A6C9E704463D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebUtilities", "WebUtilities", "{80A090C8-ED02-4DE3-875A-30DCCDBD84BA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.WebUtilities", "WebUtilities\src\Microsoft.AspNetCore.WebUtilities.csproj", "{1A866315-5FD5-4F96-BFAC-1447E3CB4514}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebUtilities", "WebUtilities\src\Microsoft.AspNetCore.WebUtilities.csproj", "{1A866315-5FD5-4F96-BFAC-1447E3CB4514}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.WebUtilities.Tests", "WebUtilities\test\Microsoft.AspNetCore.WebUtilities.Tests.csproj", "{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebUtilities.Tests", "WebUtilities\test\Microsoft.AspNetCore.WebUtilities.Tests.csproj", "{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Performance", "Http\perf\Microsoft.AspNetCore.Http.Performance.csproj", "{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
@ -68,9 +70,6 @@ Global
|
|||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
|
|
@ -288,6 +287,21 @@ Global
|
|||
{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x64.Build.0 = Release|Any CPU
|
||||
{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{565B7B00-96A1-49B8-9753-9E045C6527A2} = {587C3D55-6092-4B86-99F5-E9772C9C1ADB}
|
||||
|
|
@ -308,5 +322,9 @@ Global
|
|||
{2378049E-ABE9-4843-AAC7-A6C9E704463D} = {391FBA36-BEEB-411A-A588-3F83901C0C1A}
|
||||
{1A866315-5FD5-4F96-BFAC-1447E3CB4514} = {80A090C8-ED02-4DE3-875A-30DCCDBD84BA}
|
||||
{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8} = {80A090C8-ED02-4DE3-875A-30DCCDBD84BA}
|
||||
{8C635944-51F0-4BB0-A89E-CA49A7D9BE7F} = {FB2DCA0F-EB9E-425B-ABBC-D543DBEC090F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {39F96525-F455-424F-ABDB-33DB59861EA6}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
Loading…
Reference in New Issue