Remove custom stream->pipe wrappers and make BodyReader/BodyWriter read-only (#10154)
This commit is contained in:
parent
1165a6fb16
commit
a96642f6fd
|
|
@ -250,7 +250,7 @@ namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
protected HttpRequest() { }
|
protected HttpRequest() { }
|
||||||
public abstract System.IO.Stream Body { get; set; }
|
public abstract System.IO.Stream Body { get; set; }
|
||||||
public virtual System.IO.Pipelines.PipeReader BodyReader { get { throw null; } set { } }
|
public virtual System.IO.Pipelines.PipeReader BodyReader { get { throw null; } }
|
||||||
public abstract long? ContentLength { get; set; }
|
public abstract long? ContentLength { get; set; }
|
||||||
public abstract string ContentType { get; set; }
|
public abstract string ContentType { get; set; }
|
||||||
public abstract Microsoft.AspNetCore.Http.IRequestCookieCollection Cookies { get; set; }
|
public abstract Microsoft.AspNetCore.Http.IRequestCookieCollection Cookies { get; set; }
|
||||||
|
|
@ -274,7 +274,7 @@ namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
protected HttpResponse() { }
|
protected HttpResponse() { }
|
||||||
public abstract System.IO.Stream Body { get; set; }
|
public abstract System.IO.Stream Body { get; set; }
|
||||||
public virtual System.IO.Pipelines.PipeWriter BodyWriter { get { throw null; } set { } }
|
public virtual System.IO.Pipelines.PipeWriter BodyWriter { get { throw null; } }
|
||||||
public abstract long? ContentLength { get; set; }
|
public abstract long? ContentLength { get; set; }
|
||||||
public abstract string ContentType { get; set; }
|
public abstract string ContentType { get; set; }
|
||||||
public abstract Microsoft.AspNetCore.Http.IResponseCookies Cookies { get; }
|
public abstract Microsoft.AspNetCore.Http.IResponseCookies Cookies { get; }
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,9 @@ namespace Microsoft.AspNetCore.Http
|
||||||
public abstract Stream Body { get; set; }
|
public abstract Stream Body { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the request body pipe <see cref="PipeReader"/>.
|
/// Gets the request body pipe <see cref="PipeReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual PipeReader BodyReader { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
public virtual PipeReader BodyReader { get => throw new NotImplementedException(); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks the Content-Type header for form types.
|
/// Checks the Content-Type header for form types.
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,9 @@ namespace Microsoft.AspNetCore.Http
|
||||||
public abstract Stream Body { get; set; }
|
public abstract Stream Body { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the response body pipe <see cref="PipeWriter"/>
|
/// Gets the response body pipe <see cref="PipeWriter"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the value for the <c>Content-Length</c> response header.
|
/// Gets or sets the value for the <c>Content-Length</c> response header.
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.Linq;
|
||||||
using System.IO.Pipelines.Tests;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -36,15 +35,12 @@ namespace Microsoft.AspNetCore.Http
|
||||||
[MemberData(nameof(Encodings))]
|
[MemberData(nameof(Encodings))]
|
||||||
public async Task WritingTextThatRequiresMultipleSegmentsWorks(Encoding encoding)
|
public async Task WritingTextThatRequiresMultipleSegmentsWorks(Encoding encoding)
|
||||||
{
|
{
|
||||||
// Need to change the StreamPipeWriter with a capped MemoryPool
|
|
||||||
var memoryPool = new TestMemoryPool(maxBufferSize: 16);
|
|
||||||
var outputStream = new MemoryStream();
|
var outputStream = new MemoryStream();
|
||||||
var streamPipeWriter = new StreamPipeWriter(outputStream, minimumSegmentSize: 0, memoryPool);
|
|
||||||
|
|
||||||
HttpContext context = new DefaultHttpContext();
|
HttpContext context = new DefaultHttpContext();
|
||||||
context.Response.BodyWriter = streamPipeWriter;
|
context.Response.Body = outputStream;
|
||||||
|
|
||||||
var inputString = "昨日すき焼きを食べました";
|
var inputString = string.Concat(Enumerable.Repeat("昨日すき焼きを食べました", 1000));
|
||||||
var expected = encoding.GetBytes(inputString);
|
var expected = encoding.GetBytes(inputString);
|
||||||
await context.Response.WriteAsync(inputString, encoding);
|
await context.Response.WriteAsync(inputString, encoding);
|
||||||
|
|
||||||
|
|
@ -52,11 +48,8 @@ namespace Microsoft.AspNetCore.Http
|
||||||
var actual = new byte[expected.Length];
|
var actual = new byte[expected.Length];
|
||||||
var length = outputStream.Read(actual);
|
var length = outputStream.Read(actual);
|
||||||
|
|
||||||
var res1 = encoding.GetString(actual);
|
|
||||||
var res2 = encoding.GetString(expected);
|
|
||||||
Assert.Equal(expected.Length, length);
|
Assert.Equal(expected.Length, length);
|
||||||
Assert.Equal(expected, actual);
|
Assert.Equal(expected, actual);
|
||||||
streamPipeWriter.Complete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
}
|
}
|
||||||
public partial interface IRequestBodyPipeFeature
|
public partial interface IRequestBodyPipeFeature
|
||||||
{
|
{
|
||||||
System.IO.Pipelines.PipeReader Reader { get; set; }
|
System.IO.Pipelines.PipeReader Reader { get; }
|
||||||
}
|
}
|
||||||
public partial interface IRequestCookiesFeature
|
public partial interface IRequestCookiesFeature
|
||||||
{
|
{
|
||||||
|
|
@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
}
|
}
|
||||||
public partial interface IResponseBodyPipeFeature
|
public partial interface IResponseBodyPipeFeature
|
||||||
{
|
{
|
||||||
System.IO.Pipelines.PipeWriter Writer { get; set; }
|
System.IO.Pipelines.PipeWriter Writer { get; }
|
||||||
}
|
}
|
||||||
public partial interface IResponseCookiesFeature
|
public partial interface IResponseCookiesFeature
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="PipeReader"/> representing the request body, if any.
|
/// A <see cref="PipeReader"/> representing the request body, if any.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PipeReader Reader { get; set; }
|
PipeReader Reader { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,6 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="PipeWriter"/> representing the response body, if any.
|
/// A <see cref="PipeWriter"/> representing the response body, if any.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PipeWriter Writer { get; set; }
|
PipeWriter Writer { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
// 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.Pipelines;
|
|
||||||
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,38 +0,0 @@
|
||||||
// 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.IO;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http
|
|
||||||
{
|
|
||||||
public class StreamPipeWriterBenchmark
|
|
||||||
{
|
|
||||||
private Stream _memoryStream;
|
|
||||||
private StreamPipeWriter _pipeWriter;
|
|
||||||
private static byte[] _helloWorldBytes = Encoding.ASCII.GetBytes("Hello World");
|
|
||||||
private static byte[] _largeWrite = Encoding.ASCII.GetBytes(new string('a', 50000));
|
|
||||||
|
|
||||||
[IterationSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_memoryStream = new NoopStream();
|
|
||||||
_pipeWriter = new StreamPipeWriter(_memoryStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public async Task WriteHelloWorld()
|
|
||||||
{
|
|
||||||
await _pipeWriter.WriteAsync(_helloWorldBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public async Task WriteHelloWorldLargeWrite()
|
|
||||||
{
|
|
||||||
await _pipeWriter.WriteAsync(_largeWrite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -236,7 +236,7 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
public partial class RequestBodyPipeFeature : Microsoft.AspNetCore.Http.Features.IRequestBodyPipeFeature
|
public partial class RequestBodyPipeFeature : Microsoft.AspNetCore.Http.Features.IRequestBodyPipeFeature
|
||||||
{
|
{
|
||||||
public RequestBodyPipeFeature(Microsoft.AspNetCore.Http.HttpContext context) { }
|
public RequestBodyPipeFeature(Microsoft.AspNetCore.Http.HttpContext context) { }
|
||||||
public System.IO.Pipelines.PipeReader Reader { get { throw null; } set { } }
|
public System.IO.Pipelines.PipeReader Reader { get { throw null; } }
|
||||||
}
|
}
|
||||||
public partial class RequestCookiesFeature : Microsoft.AspNetCore.Http.Features.IRequestCookiesFeature
|
public partial class RequestCookiesFeature : Microsoft.AspNetCore.Http.Features.IRequestCookiesFeature
|
||||||
{
|
{
|
||||||
|
|
@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
public partial class ResponseBodyPipeFeature : Microsoft.AspNetCore.Http.Features.IResponseBodyPipeFeature
|
public partial class ResponseBodyPipeFeature : Microsoft.AspNetCore.Http.Features.IResponseBodyPipeFeature
|
||||||
{
|
{
|
||||||
public ResponseBodyPipeFeature(Microsoft.AspNetCore.Http.HttpContext context) { }
|
public ResponseBodyPipeFeature(Microsoft.AspNetCore.Http.HttpContext context) { }
|
||||||
public System.IO.Pipelines.PipeWriter Writer { get { throw null; } set { } }
|
public System.IO.Pipelines.PipeWriter Writer { get { throw null; } }
|
||||||
}
|
}
|
||||||
public partial class ResponseCookiesFeature : Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature
|
public partial class ResponseCookiesFeature : Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature
|
||||||
{
|
{
|
||||||
|
|
@ -326,7 +326,7 @@ namespace Microsoft.AspNetCore.Http.Internal
|
||||||
{
|
{
|
||||||
public DefaultHttpRequest(Microsoft.AspNetCore.Http.DefaultHttpContext context) { }
|
public DefaultHttpRequest(Microsoft.AspNetCore.Http.DefaultHttpContext context) { }
|
||||||
public override System.IO.Stream Body { get { throw null; } set { } }
|
public override System.IO.Stream Body { get { throw null; } set { } }
|
||||||
public override System.IO.Pipelines.PipeReader BodyReader { get { throw null; } set { } }
|
public override System.IO.Pipelines.PipeReader BodyReader { get { throw null; } }
|
||||||
public override long? ContentLength { get { throw null; } set { } }
|
public override long? ContentLength { get { throw null; } set { } }
|
||||||
public override string ContentType { get { throw null; } set { } }
|
public override string ContentType { get { throw null; } set { } }
|
||||||
public override Microsoft.AspNetCore.Http.IRequestCookieCollection Cookies { get { throw null; } set { } }
|
public override Microsoft.AspNetCore.Http.IRequestCookieCollection Cookies { get { throw null; } set { } }
|
||||||
|
|
@ -353,7 +353,7 @@ namespace Microsoft.AspNetCore.Http.Internal
|
||||||
{
|
{
|
||||||
public DefaultHttpResponse(Microsoft.AspNetCore.Http.DefaultHttpContext context) { }
|
public DefaultHttpResponse(Microsoft.AspNetCore.Http.DefaultHttpContext context) { }
|
||||||
public override System.IO.Stream Body { get { throw null; } set { } }
|
public override System.IO.Stream Body { get { throw null; } set { } }
|
||||||
public override System.IO.Pipelines.PipeWriter BodyWriter { get { throw null; } set { } }
|
public override System.IO.Pipelines.PipeWriter BodyWriter { get { throw null; } }
|
||||||
public override long? ContentLength { get { throw null; } set { } }
|
public override long? ContentLength { get { throw null; } set { } }
|
||||||
public override string ContentType { get { throw null; } set { } }
|
public override string ContentType { get { throw null; } set { } }
|
||||||
public override Microsoft.AspNetCore.Http.IResponseCookies Cookies { get { throw null; } }
|
public override Microsoft.AspNetCore.Http.IResponseCookies Cookies { get { throw null; } }
|
||||||
|
|
@ -492,93 +492,3 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpContextAccessor(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
|
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpContextAccessor(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace System.IO.Pipelines
|
|
||||||
{
|
|
||||||
public partial class ReadOnlyPipeStream : System.IO.Stream
|
|
||||||
{
|
|
||||||
public ReadOnlyPipeStream(System.IO.Pipelines.PipeReader pipeReader) { }
|
|
||||||
public ReadOnlyPipeStream(System.IO.Pipelines.PipeReader pipeReader, bool allowSynchronousIO) { }
|
|
||||||
public override bool CanRead { get { throw null; } }
|
|
||||||
public override bool CanSeek { get { throw null; } }
|
|
||||||
public override bool CanWrite { get { throw null; } }
|
|
||||||
public System.IO.Pipelines.PipeReader InnerPipeReader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
public override long Length { get { throw null; } }
|
|
||||||
public override long Position { get { throw null; } set { } }
|
|
||||||
public override int WriteTimeout { get { throw null; } set { } }
|
|
||||||
public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; }
|
|
||||||
public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; }
|
|
||||||
public override int EndRead(System.IAsyncResult asyncResult) { throw null; }
|
|
||||||
public override void Flush() { }
|
|
||||||
public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
|
|
||||||
public override int Read(byte[] buffer, int offset, int count) { throw null; }
|
|
||||||
public override System.Threading.Tasks.Task<int> ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
|
|
||||||
public override System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<byte> destination, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
|
||||||
public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; }
|
|
||||||
public override void SetLength(long value) { }
|
|
||||||
public override void Write(byte[] buffer, int offset, int count) { }
|
|
||||||
public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
|
|
||||||
}
|
|
||||||
public partial class StreamPipeReader : System.IO.Pipelines.PipeReader, System.IDisposable
|
|
||||||
{
|
|
||||||
public StreamPipeReader(System.IO.Stream readingStream) { }
|
|
||||||
public StreamPipeReader(System.IO.Stream readingStream, System.IO.Pipelines.StreamPipeReaderAdapterOptions options) { }
|
|
||||||
public System.IO.Stream InnerStream { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
public override void AdvanceTo(System.SequencePosition consumed) { }
|
|
||||||
public override void AdvanceTo(System.SequencePosition consumed, System.SequencePosition examined) { }
|
|
||||||
public override void CancelPendingRead() { }
|
|
||||||
public override void Complete(System.Exception exception = null) { }
|
|
||||||
public void Dispose() { }
|
|
||||||
public override void OnWriterCompleted(System.Action<System.Exception, object> callback, object state) { }
|
|
||||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
|
||||||
public override System.Threading.Tasks.ValueTask<System.IO.Pipelines.ReadResult> ReadAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
|
||||||
public override bool TryRead(out System.IO.Pipelines.ReadResult result) { throw null; }
|
|
||||||
}
|
|
||||||
public partial class StreamPipeReaderAdapterOptions
|
|
||||||
{
|
|
||||||
public const int DefaultMinimumReadThreshold = 256;
|
|
||||||
public const int DefaultMinimumSegmentSize = 4096;
|
|
||||||
public static System.IO.Pipelines.StreamPipeReaderAdapterOptions DefaultOptions;
|
|
||||||
public StreamPipeReaderAdapterOptions() { }
|
|
||||||
public StreamPipeReaderAdapterOptions(int minimumSegmentSize, int minimumReadThreshold, System.Buffers.MemoryPool<byte> memoryPool) { }
|
|
||||||
public System.Buffers.MemoryPool<byte> MemoryPool { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
|
||||||
public int MinimumReadThreshold { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
|
||||||
public int MinimumSegmentSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
|
||||||
}
|
|
||||||
public partial class StreamPipeWriter : System.IO.Pipelines.PipeWriter, System.IDisposable
|
|
||||||
{
|
|
||||||
public StreamPipeWriter(System.IO.Stream writingStream) { }
|
|
||||||
public StreamPipeWriter(System.IO.Stream writingStream, int minimumSegmentSize, System.Buffers.MemoryPool<byte> pool = null) { }
|
|
||||||
public System.IO.Stream InnerStream { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
public override void Advance(int count) { }
|
|
||||||
public override void CancelPendingFlush() { }
|
|
||||||
public override void Complete(System.Exception exception = null) { }
|
|
||||||
public void Dispose() { }
|
|
||||||
public override System.Threading.Tasks.ValueTask<System.IO.Pipelines.FlushResult> FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
|
||||||
public override System.Memory<byte> GetMemory(int sizeHint = 0) { throw null; }
|
|
||||||
public override System.Span<byte> GetSpan(int sizeHint = 0) { throw null; }
|
|
||||||
public override void OnReaderCompleted(System.Action<System.Exception, object> callback, object state) { }
|
|
||||||
}
|
|
||||||
public partial class WriteOnlyPipeStream : System.IO.Stream
|
|
||||||
{
|
|
||||||
public WriteOnlyPipeStream(System.IO.Pipelines.PipeWriter pipeWriter) { }
|
|
||||||
public WriteOnlyPipeStream(System.IO.Pipelines.PipeWriter pipeWriter, bool allowSynchronousIO) { }
|
|
||||||
public override bool CanRead { get { throw null; } }
|
|
||||||
public override bool CanSeek { get { throw null; } }
|
|
||||||
public override bool CanWrite { get { throw null; } }
|
|
||||||
public System.IO.Pipelines.PipeWriter InnerPipeWriter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
public override long Length { get { throw null; } }
|
|
||||||
public override long Position { get { throw null; } set { } }
|
|
||||||
public override int ReadTimeout { get { throw null; } set { } }
|
|
||||||
public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; }
|
|
||||||
public override void EndWrite(System.IAsyncResult asyncResult) { }
|
|
||||||
public override void Flush() { }
|
|
||||||
public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
|
|
||||||
public override int Read(byte[] buffer, int offset, int count) { throw null; }
|
|
||||||
public override System.Threading.Tasks.Task<int> ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
|
|
||||||
public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; }
|
|
||||||
public override void SetLength(long value) { }
|
|
||||||
public override void Write(byte[] buffer, int offset, int count) { }
|
|
||||||
public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
|
|
||||||
public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory<byte> source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,16 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http.Features
|
namespace Microsoft.AspNetCore.Http.Features
|
||||||
{
|
{
|
||||||
public class RequestBodyPipeFeature : IRequestBodyPipeFeature
|
public class RequestBodyPipeFeature : IRequestBodyPipeFeature
|
||||||
{
|
{
|
||||||
private StreamPipeReader _internalPipeReader;
|
private PipeReader _internalPipeReader;
|
||||||
private PipeReader _userSetPipeReader;
|
private Stream _streamInstanceWhenWrapped;
|
||||||
private HttpContext _context;
|
private HttpContext _context;
|
||||||
|
|
||||||
public RequestBodyPipeFeature(HttpContext context)
|
public RequestBodyPipeFeature(HttpContext context)
|
||||||
|
|
@ -25,25 +27,21 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_userSetPipeReader != null)
|
|
||||||
{
|
|
||||||
return _userSetPipeReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_internalPipeReader == null ||
|
if (_internalPipeReader == null ||
|
||||||
!object.ReferenceEquals(_internalPipeReader.InnerStream, _context.Request.Body))
|
!ReferenceEquals(_streamInstanceWhenWrapped, _context.Request.Body))
|
||||||
{
|
{
|
||||||
_internalPipeReader = new StreamPipeReader(_context.Request.Body);
|
_streamInstanceWhenWrapped = _context.Request.Body;
|
||||||
_context.Response.RegisterForDispose(_internalPipeReader);
|
_internalPipeReader = PipeReader.Create(_context.Request.Body);
|
||||||
|
|
||||||
|
_context.Response.OnCompleted((self) =>
|
||||||
|
{
|
||||||
|
((PipeReader)self).Complete();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, _internalPipeReader);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _internalPipeReader;
|
return _internalPipeReader;
|
||||||
}
|
}
|
||||||
set
|
|
||||||
{
|
|
||||||
_userSetPipeReader = value ?? throw new ArgumentNullException(nameof(value));
|
|
||||||
// TODO set the request body Stream to an adapted pipe https://github.com/aspnet/AspNetCore/issues/3971
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,16 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http.Features
|
namespace Microsoft.AspNetCore.Http.Features
|
||||||
{
|
{
|
||||||
public class ResponseBodyPipeFeature : IResponseBodyPipeFeature
|
public class ResponseBodyPipeFeature : IResponseBodyPipeFeature
|
||||||
{
|
{
|
||||||
private StreamPipeWriter _internalPipeWriter;
|
private PipeWriter _internalPipeWriter;
|
||||||
private PipeWriter _userSetPipeWriter;
|
private Stream _streamInstanceWhenWrapped;
|
||||||
private HttpContext _context;
|
private HttpContext _context;
|
||||||
|
|
||||||
public ResponseBodyPipeFeature(HttpContext context)
|
public ResponseBodyPipeFeature(HttpContext context)
|
||||||
|
|
@ -25,25 +27,21 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_userSetPipeWriter != null)
|
|
||||||
{
|
|
||||||
return _userSetPipeWriter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_internalPipeWriter == null ||
|
if (_internalPipeWriter == null ||
|
||||||
!object.ReferenceEquals(_internalPipeWriter.InnerStream, _context.Response.Body))
|
!ReferenceEquals(_streamInstanceWhenWrapped, _context.Response.Body))
|
||||||
{
|
{
|
||||||
_internalPipeWriter = new StreamPipeWriter(_context.Response.Body);
|
_streamInstanceWhenWrapped = _context.Response.Body;
|
||||||
_context.Response.RegisterForDispose(_internalPipeWriter);
|
_internalPipeWriter = PipeWriter.Create(_context.Response.Body);
|
||||||
|
|
||||||
|
_context.Response.OnCompleted((self) =>
|
||||||
|
{
|
||||||
|
((PipeWriter)self).Complete();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, _internalPipeWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _internalPipeWriter;
|
return _internalPipeWriter;
|
||||||
}
|
}
|
||||||
set
|
|
||||||
{
|
|
||||||
_userSetPipeWriter = value ?? throw new ArgumentNullException(nameof(value));
|
|
||||||
// TODO set the response body Stream to an adapted pipe https://github.com/aspnet/AspNetCore/issues/3971
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,6 @@ namespace Microsoft.AspNetCore.Http.Internal
|
||||||
public override PipeReader BodyReader
|
public override PipeReader BodyReader
|
||||||
{
|
{
|
||||||
get { return RequestBodyPipeFeature.Reader; }
|
get { return RequestBodyPipeFeature.Reader; }
|
||||||
set { RequestBodyPipeFeature.Reader = value; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FeatureInterfaces
|
struct FeatureInterfaces
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,6 @@ namespace Microsoft.AspNetCore.Http.Internal
|
||||||
public override PipeWriter BodyWriter
|
public override PipeWriter BodyWriter
|
||||||
{
|
{
|
||||||
get { return ResponseBodyPipeFeature.Writer; }
|
get { return ResponseBodyPipeFeature.Writer; }
|
||||||
set { ResponseBodyPipeFeature.Writer = value; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnStarting(Func<object, Task> callback, object state)
|
public override void OnStarting(Func<object, Task> callback, object state)
|
||||||
|
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
// 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.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a read-only Stream backed by a PipeReader
|
|
||||||
/// </summary>
|
|
||||||
public class ReadOnlyPipeStream : Stream
|
|
||||||
{
|
|
||||||
private bool _allowSynchronousIO = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new ReadOnlyPipeStream
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pipeReader">The PipeReader to read from.</param>
|
|
||||||
public ReadOnlyPipeStream(PipeReader pipeReader) :
|
|
||||||
this(pipeReader, allowSynchronousIO: true)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new ReadOnlyPipeStream
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pipeReader">The PipeReader to read from.</param>
|
|
||||||
/// <param name="allowSynchronousIO">Whether synchronous IO is allowed.</param>
|
|
||||||
public ReadOnlyPipeStream(PipeReader pipeReader, bool allowSynchronousIO)
|
|
||||||
{
|
|
||||||
_allowSynchronousIO = allowSynchronousIO;
|
|
||||||
InnerPipeReader = pipeReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool CanSeek => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool CanRead => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool CanWrite => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override long Length => throw new NotSupportedException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override long Position
|
|
||||||
{
|
|
||||||
get => throw new NotSupportedException();
|
|
||||||
set => throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int WriteTimeout
|
|
||||||
{
|
|
||||||
get => throw new NotSupportedException();
|
|
||||||
set => throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PipeReader InnerPipeReader { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
|
||||||
=> throw new NotSupportedException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
=> throw new NotSupportedException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void SetLength(long value)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
if (!_allowSynchronousIO)
|
|
||||||
{
|
|
||||||
ThrowHelper.ThrowInvalidOperationException_SynchronousReadsDisallowed();
|
|
||||||
}
|
|
||||||
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
|
||||||
{
|
|
||||||
var task = ReadAsync(buffer, offset, count, default, state);
|
|
||||||
if (callback != null)
|
|
||||||
{
|
|
||||||
task.ContinueWith(t => callback.Invoke(t));
|
|
||||||
}
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int EndRead(IAsyncResult asyncResult)
|
|
||||||
{
|
|
||||||
return ((Task<int>)asyncResult).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
|
|
||||||
{
|
|
||||||
var tcs = new TaskCompletionSource<int>(state);
|
|
||||||
var task = ReadAsync(buffer, offset, count, cancellationToken);
|
|
||||||
task.ContinueWith((task2, state2) =>
|
|
||||||
{
|
|
||||||
var tcs2 = (TaskCompletionSource<int>)state2;
|
|
||||||
if (task2.IsCanceled)
|
|
||||||
{
|
|
||||||
tcs2.SetCanceled();
|
|
||||||
}
|
|
||||||
else if (task2.IsFaulted)
|
|
||||||
{
|
|
||||||
tcs2.SetException(task2.Exception);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tcs2.SetResult(task2.Result);
|
|
||||||
}
|
|
||||||
}, tcs, cancellationToken);
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return ReadAsyncInternal(destination, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask<int> ReadAsyncInternal(Memory<byte> buffer, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var result = await InnerPipeReader.ReadAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (result.IsCanceled)
|
|
||||||
{
|
|
||||||
ThrowHelper.ThrowOperationCanceledException_ReadCanceled();
|
|
||||||
}
|
|
||||||
|
|
||||||
var readableBuffer = result.Buffer;
|
|
||||||
var readableBufferLength = readableBuffer.Length;
|
|
||||||
|
|
||||||
var consumed = readableBuffer.End;
|
|
||||||
var actual = 0;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (readableBufferLength != 0)
|
|
||||||
{
|
|
||||||
actual = (int)Math.Min(readableBufferLength, buffer.Length);
|
|
||||||
|
|
||||||
var slice = actual == readableBufferLength ? readableBuffer : readableBuffer.Slice(0, actual);
|
|
||||||
consumed = slice.End;
|
|
||||||
slice.CopyTo(buffer.Span);
|
|
||||||
|
|
||||||
return actual;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.IsCompleted)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
InnerPipeReader.AdvanceTo(consumed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (destination == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(destination));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bufferSize <= 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
return CopyToAsyncInternal(destination, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CopyToAsyncInternal(Stream destination, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var result = await InnerPipeReader.ReadAsync(cancellationToken);
|
|
||||||
var readableBuffer = result.Buffer;
|
|
||||||
var readableBufferLength = readableBuffer.Length;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (readableBufferLength != 0)
|
|
||||||
{
|
|
||||||
foreach (var memory in readableBuffer)
|
|
||||||
{
|
|
||||||
await destination.WriteAsync(memory, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.IsCompleted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
InnerPipeReader.AdvanceTo(readableBuffer.End);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,393 +0,0 @@
|
||||||
// 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;
|
|
||||||
using System.Runtime.ExceptionServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implements PipeReader using an underlying stream.
|
|
||||||
/// </summary>
|
|
||||||
public class StreamPipeReader : PipeReader, IDisposable
|
|
||||||
{
|
|
||||||
private readonly int _bufferSize;
|
|
||||||
private readonly int _minimumReadThreshold;
|
|
||||||
private readonly MemoryPool<byte> _pool;
|
|
||||||
|
|
||||||
private CancellationTokenSource _internalTokenSource;
|
|
||||||
private bool _isReaderCompleted;
|
|
||||||
private bool _isStreamCompleted;
|
|
||||||
private ExceptionDispatchInfo _exceptionInfo;
|
|
||||||
|
|
||||||
private BufferSegment _readHead;
|
|
||||||
private int _readIndex;
|
|
||||||
|
|
||||||
private BufferSegment _readTail;
|
|
||||||
private long _bufferedBytes;
|
|
||||||
private bool _examinedEverything;
|
|
||||||
private object _lock = new object();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new StreamPipeReader.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="readingStream">The stream to read from.</param>
|
|
||||||
public StreamPipeReader(Stream readingStream)
|
|
||||||
: this(readingStream, StreamPipeReaderAdapterOptions.DefaultOptions)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new StreamPipeReader.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="readingStream">The stream to read from.</param>
|
|
||||||
/// <param name="options">The options to use.</param>
|
|
||||||
public StreamPipeReader(Stream readingStream, StreamPipeReaderAdapterOptions options)
|
|
||||||
{
|
|
||||||
InnerStream = readingStream ?? throw new ArgumentNullException(nameof(readingStream));
|
|
||||||
|
|
||||||
if (options == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(options));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.MinimumReadThreshold <= 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(options.MinimumReadThreshold));
|
|
||||||
}
|
|
||||||
|
|
||||||
_minimumReadThreshold = Math.Min(options.MinimumReadThreshold, options.MinimumSegmentSize);
|
|
||||||
_pool = options.MemoryPool == MemoryPool<byte>.Shared ? null : options.MemoryPool;
|
|
||||||
_bufferSize = _pool == null ? options.MinimumSegmentSize : Math.Min(options.MinimumSegmentSize, _pool.MaxBufferSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the inner stream that is being read from.
|
|
||||||
/// </summary>
|
|
||||||
public Stream InnerStream { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void AdvanceTo(SequencePosition consumed)
|
|
||||||
{
|
|
||||||
AdvanceTo(consumed, consumed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CancellationTokenSource InternalTokenSource
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_internalTokenSource == null)
|
|
||||||
{
|
|
||||||
_internalTokenSource = new CancellationTokenSource();
|
|
||||||
}
|
|
||||||
return _internalTokenSource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_internalTokenSource = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 (_isReaderCompleted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isReaderCompleted = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_isStreamCompleted)
|
|
||||||
{
|
|
||||||
return new ReadResult(buffer: default, isCanceled: false, isCompleted: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
var reg = new CancellationTokenRegistration();
|
|
||||||
if (cancellationToken.CanBeCanceled)
|
|
||||||
{
|
|
||||||
reg = cancellationToken.Register(state => ((StreamPipeReader)state).Cancel(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
using (reg)
|
|
||||||
{
|
|
||||||
var isCanceled = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AllocateReadTail();
|
|
||||||
#if NETCOREAPP3_0
|
|
||||||
var length = await InnerStream.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;
|
|
||||||
|
|
||||||
if (length == 0)
|
|
||||||
{
|
|
||||||
_isStreamCompleted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 (_isReaderCompleted)
|
|
||||||
{
|
|
||||||
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 || _isStreamCompleted))
|
|
||||||
{
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AllocateReadTail()
|
|
||||||
{
|
|
||||||
if (_readHead == null)
|
|
||||||
{
|
|
||||||
Debug.Assert(_readTail == null);
|
|
||||||
_readHead = AllocateSegment();
|
|
||||||
_readTail = _readHead;
|
|
||||||
}
|
|
||||||
else if (_readTail.WritableBytes < _minimumReadThreshold)
|
|
||||||
{
|
|
||||||
CreateNewTailSegment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateNewTailSegment()
|
|
||||||
{
|
|
||||||
BufferSegment nextSegment = AllocateSegment();
|
|
||||||
_readTail.SetNext(nextSegment);
|
|
||||||
_readTail = nextSegment;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BufferSegment AllocateSegment()
|
|
||||||
{
|
|
||||||
var nextSegment = new BufferSegment();
|
|
||||||
|
|
||||||
if (_pool is null)
|
|
||||||
{
|
|
||||||
nextSegment.SetMemory(ArrayPool<byte>.Shared.Rent(_bufferSize));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nextSegment.SetMemory(_pool.Rent(_bufferSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextSegment;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Cancel()
|
|
||||||
{
|
|
||||||
InternalTokenSource.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private bool IsCompletedOrThrow()
|
|
||||||
{
|
|
||||||
if (!_isStreamCompleted)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_exceptionInfo != null)
|
|
||||||
{
|
|
||||||
ThrowLatchedException();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
private void ThrowLatchedException()
|
|
||||||
{
|
|
||||||
_exceptionInfo.Throw();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
// 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.Text;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines
|
|
||||||
{
|
|
||||||
public class StreamPipeReaderAdapterOptions
|
|
||||||
{
|
|
||||||
public static StreamPipeReaderAdapterOptions DefaultOptions = new StreamPipeReaderAdapterOptions();
|
|
||||||
public const int DefaultMinimumSegmentSize = 4096;
|
|
||||||
public const int DefaultMinimumReadThreshold = 256;
|
|
||||||
|
|
||||||
public StreamPipeReaderAdapterOptions()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public StreamPipeReaderAdapterOptions(int minimumSegmentSize, int minimumReadThreshold, MemoryPool<byte> memoryPool)
|
|
||||||
{
|
|
||||||
MinimumSegmentSize = minimumSegmentSize;
|
|
||||||
MinimumReadThreshold = minimumReadThreshold;
|
|
||||||
MemoryPool = memoryPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int MinimumSegmentSize { get; set; } = DefaultMinimumSegmentSize;
|
|
||||||
|
|
||||||
public int MinimumReadThreshold { get; set; } = DefaultMinimumReadThreshold;
|
|
||||||
|
|
||||||
public MemoryPool<byte> MemoryPool { get; set; } = MemoryPool<byte>.Shared;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,347 +0,0 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implements PipeWriter using a underlying stream.
|
|
||||||
/// </summary>
|
|
||||||
public class StreamPipeWriter : PipeWriter, IDisposable
|
|
||||||
{
|
|
||||||
private readonly int _minimumSegmentSize;
|
|
||||||
private int _bytesWritten;
|
|
||||||
|
|
||||||
private List<CompletedBuffer> _completedSegments;
|
|
||||||
private Memory<byte> _currentSegment;
|
|
||||||
private object _currentSegmentOwner;
|
|
||||||
private MemoryPool<byte> _pool;
|
|
||||||
private int _position;
|
|
||||||
|
|
||||||
private CancellationTokenSource _internalTokenSource;
|
|
||||||
private bool _isCompleted;
|
|
||||||
private object _lockObject = new object();
|
|
||||||
|
|
||||||
private CancellationTokenSource InternalTokenSource
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
if (_internalTokenSource == null)
|
|
||||||
{
|
|
||||||
_internalTokenSource = new CancellationTokenSource();
|
|
||||||
}
|
|
||||||
return _internalTokenSource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new StreamPipeWrapper
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="writingStream">The stream to write to</param>
|
|
||||||
public StreamPipeWriter(Stream writingStream) : this(writingStream, 4096)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public StreamPipeWriter(Stream writingStream, int minimumSegmentSize, MemoryPool<byte> pool = null)
|
|
||||||
{
|
|
||||||
_minimumSegmentSize = minimumSegmentSize;
|
|
||||||
InnerStream = writingStream;
|
|
||||||
_pool = pool == MemoryPool<byte>.Shared ? null : pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the inner stream that is being written to.
|
|
||||||
/// </summary>
|
|
||||||
public Stream InnerStream { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Advance(int count)
|
|
||||||
{
|
|
||||||
if (_currentSegment.IsEmpty) // TODO confirm this
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("No writing operation. Make sure GetMemory() was called.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count >= 0)
|
|
||||||
{
|
|
||||||
if (_currentSegment.Length < _position + count)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Can't advance past buffer size.");
|
|
||||||
}
|
|
||||||
_bytesWritten += count;
|
|
||||||
_position += count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Memory<byte> GetMemory(int sizeHint = 0)
|
|
||||||
{
|
|
||||||
if (_isCompleted)
|
|
||||||
{
|
|
||||||
ThrowHelper.ThrowInvalidOperationException_NoWritingAllowed();
|
|
||||||
}
|
|
||||||
if (sizeHint < 0)
|
|
||||||
{
|
|
||||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(sizeHint));
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureCapacity(sizeHint);
|
|
||||||
|
|
||||||
return _currentSegment.Slice(_position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Span<byte> GetSpan(int sizeHint = 0)
|
|
||||||
{
|
|
||||||
if (_isCompleted)
|
|
||||||
{
|
|
||||||
ThrowHelper.ThrowInvalidOperationException_NoWritingAllowed();
|
|
||||||
}
|
|
||||||
if (sizeHint < 0)
|
|
||||||
{
|
|
||||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(sizeHint));
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureCapacity(sizeHint);
|
|
||||||
|
|
||||||
return _currentSegment.Span.Slice(_position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void CancelPendingFlush()
|
|
||||||
{
|
|
||||||
Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Complete(Exception exception = null)
|
|
||||||
{
|
|
||||||
if (_isCompleted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispose();
|
|
||||||
// We still want to cleanup segments before throwing an exception.
|
|
||||||
if (_bytesWritten > 0 && exception == null)
|
|
||||||
{
|
|
||||||
ThrowHelper.ThrowInvalidOperationException_DataNotAllFlushed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void OnReaderCompleted(Action<Exception, object> callback, object state)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("OnReaderCompleted isn't supported in StreamPipeWrapper.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (_bytesWritten == 0)
|
|
||||||
{
|
|
||||||
return new ValueTask<FlushResult>(new FlushResult(isCanceled: false, _isCompleted));
|
|
||||||
}
|
|
||||||
|
|
||||||
return FlushAsyncInternal(cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Cancel()
|
|
||||||
{
|
|
||||||
InternalTokenSource.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask<FlushResult> FlushAsyncInternal(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
// Write all completed segments and whatever remains in the current segment
|
|
||||||
// and flush the result.
|
|
||||||
var reg = new CancellationTokenRegistration();
|
|
||||||
if (cancellationToken.CanBeCanceled)
|
|
||||||
{
|
|
||||||
reg = cancellationToken.Register(state => ((StreamPipeWriter)state).Cancel(), this);
|
|
||||||
}
|
|
||||||
using (reg)
|
|
||||||
{
|
|
||||||
var localToken = InternalTokenSource.Token;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_completedSegments != null && _completedSegments.Count > 0)
|
|
||||||
{
|
|
||||||
var count = _completedSegments.Count;
|
|
||||||
for (var i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
var segment = _completedSegments[0];
|
|
||||||
#if NETCOREAPP3_0
|
|
||||||
await InnerStream.WriteAsync(segment.Buffer.Slice(0, segment.Length), localToken);
|
|
||||||
#elif NETSTANDARD2_0
|
|
||||||
MemoryMarshal.TryGetArray<byte>(segment.Buffer, out var arraySegment);
|
|
||||||
await InnerStream.WriteAsync(arraySegment.Array, 0, segment.Length, localToken);
|
|
||||||
#else
|
|
||||||
#error Target frameworks need to be updated.
|
|
||||||
#endif
|
|
||||||
_bytesWritten -= segment.Length;
|
|
||||||
segment.Return();
|
|
||||||
_completedSegments.RemoveAt(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_currentSegment.IsEmpty)
|
|
||||||
{
|
|
||||||
#if NETCOREAPP3_0
|
|
||||||
await InnerStream.WriteAsync(_currentSegment.Slice(0, _position), localToken);
|
|
||||||
#elif NETSTANDARD2_0
|
|
||||||
MemoryMarshal.TryGetArray<byte>(_currentSegment, out var arraySegment);
|
|
||||||
await InnerStream.WriteAsync(arraySegment.Array, 0, _position, localToken);
|
|
||||||
#else
|
|
||||||
#error Target frameworks need to be updated.
|
|
||||||
#endif
|
|
||||||
_bytesWritten -= _position;
|
|
||||||
_position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await InnerStream.FlushAsync(localToken);
|
|
||||||
|
|
||||||
return new FlushResult(isCanceled: false, _isCompleted);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// Remove the cancellation token such that the next time Flush is called
|
|
||||||
// A new CTS is created.
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
_internalTokenSource = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catch any cancellation and translate it into setting isCanceled = true
|
|
||||||
return new FlushResult(isCanceled: true, _isCompleted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureCapacity(int sizeHint)
|
|
||||||
{
|
|
||||||
// This does the Right Thing. It only subtracts _position from the current segment length if it's non-null.
|
|
||||||
// If _currentSegment is null, it returns 0.
|
|
||||||
var remainingSize = _currentSegment.Length - _position;
|
|
||||||
|
|
||||||
// If the sizeHint is 0, any capacity will do
|
|
||||||
// Otherwise, the buffer must have enough space for the entire size hint, or we need to add a segment.
|
|
||||||
if ((sizeHint == 0 && remainingSize > 0) || (sizeHint > 0 && remainingSize >= sizeHint))
|
|
||||||
{
|
|
||||||
// We have capacity in the current segment
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddSegment(sizeHint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddSegment(int sizeHint = 0)
|
|
||||||
{
|
|
||||||
if (_currentSegment.Length != 0)
|
|
||||||
{
|
|
||||||
// We're adding a segment to the list
|
|
||||||
if (_completedSegments == null)
|
|
||||||
{
|
|
||||||
_completedSegments = new List<CompletedBuffer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position might be less than the segment length if there wasn't enough space to satisfy the sizeHint when
|
|
||||||
// GetMemory was called. In that case we'll take the current segment and call it "completed", but need to
|
|
||||||
// ignore any empty space in it.
|
|
||||||
_completedSegments.Add(new CompletedBuffer(_currentSegmentOwner, _currentSegment, _position));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_pool is null)
|
|
||||||
{
|
|
||||||
_currentSegment = ArrayPool<byte>.Shared.Rent(Math.Max(sizeHint, _minimumSegmentSize));
|
|
||||||
_currentSegmentOwner = _currentSegment;
|
|
||||||
}
|
|
||||||
else if (sizeHint <= _pool.MaxBufferSize)
|
|
||||||
{
|
|
||||||
// Get a new buffer using the minimum segment size, unless the size hint is larger than a single segment.
|
|
||||||
// Also, the size cannot be larger than the MaxBufferSize of the MemoryPool
|
|
||||||
var owner = _pool.Rent(Math.Clamp(sizeHint, _minimumSegmentSize, _pool.MaxBufferSize));
|
|
||||||
_currentSegment = owner.Memory;
|
|
||||||
_currentSegmentOwner = owner;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_currentSegment = new byte[sizeHint];
|
|
||||||
}
|
|
||||||
|
|
||||||
_position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_isCompleted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isCompleted = true;
|
|
||||||
|
|
||||||
_internalTokenSource?.Dispose();
|
|
||||||
|
|
||||||
if (_completedSegments != null)
|
|
||||||
{
|
|
||||||
foreach (var segment in _completedSegments)
|
|
||||||
{
|
|
||||||
segment.Return();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DisposeOwner(_currentSegmentOwner);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DisposeOwner(object owner)
|
|
||||||
{
|
|
||||||
if (owner is IMemoryOwner<byte> memoryOwner)
|
|
||||||
{
|
|
||||||
memoryOwner.Dispose();
|
|
||||||
}
|
|
||||||
else if (owner is byte[] array)
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Holds a byte[] from the pool and a size value. Basically a Memory but guaranteed to be backed by an ArrayPool byte[], so that we know we can return it.
|
|
||||||
/// </summary>
|
|
||||||
private readonly struct CompletedBuffer
|
|
||||||
{
|
|
||||||
private readonly object _memoryOwner;
|
|
||||||
|
|
||||||
public Memory<byte> Buffer { get; }
|
|
||||||
public int Length { get; }
|
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Span => Buffer.Span;
|
|
||||||
|
|
||||||
public CompletedBuffer(object owner, Memory<byte> buffer, int length)
|
|
||||||
{
|
|
||||||
_memoryOwner = owner;
|
|
||||||
|
|
||||||
Buffer = buffer;
|
|
||||||
Length = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Return()
|
|
||||||
{
|
|
||||||
DisposeOwner(_memoryOwner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
// 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.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines
|
|
||||||
{
|
|
||||||
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.");
|
|
||||||
|
|
||||||
public static void ThrowInvalidOperationException_SynchronousReadsDisallowed() => throw CreateInvalidOperationException_SynchronousReadsDisallowed();
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public static Exception CreateInvalidOperationException_SynchronousReadsDisallowed() => new InvalidOperationException("Synchronous operations are disallowed. Call ReadAsync or set allowSynchronousIO to true instead.");
|
|
||||||
|
|
||||||
public static void ThrowInvalidOperationException_SynchronousWritesDisallowed() => throw CreateInvalidOperationException_SynchronousWritesDisallowed();
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public static Exception CreateInvalidOperationException_SynchronousWritesDisallowed() => new InvalidOperationException("Synchronous operations are disallowed. Call WriteAsync or set allowSynchronousIO to true instead.");
|
|
||||||
|
|
||||||
public static void ThrowInvalidOperationException_SynchronousFlushesDisallowed() => throw CreateInvalidOperationException_SynchronousFlushesDisallowed();
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public static Exception CreateInvalidOperationException_SynchronousFlushesDisallowed() => new InvalidOperationException("Synchronous operations are disallowed. Call FlushAsync or set allowSynchronousIO to true instead.");
|
|
||||||
|
|
||||||
public static void ThrowInvalidOperationException_DataNotAllFlushed() => throw CreateInvalidOperationException_DataNotAllFlushed();
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public static Exception CreateInvalidOperationException_DataNotAllFlushed() => new InvalidOperationException("Complete called without flushing the StreamPipeWriter. Call FlushAsync() before calling Complete().");
|
|
||||||
|
|
||||||
public static void ThrowInvalidOperationException_NoWritingAllowed() => throw CreateInvalidOperationException_NoWritingAllowed();
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public static Exception CreateInvalidOperationException_NoWritingAllowed() => new InvalidOperationException("Writing is not allowed after writer was completed.");
|
|
||||||
|
|
||||||
public static void ThrowArgumentOutOfRangeException(string argument) => throw CreateArgumentOutOfRangeException(argument);
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public static Exception CreateArgumentOutOfRangeException(string argument) => new ArgumentOutOfRangeException(argument);
|
|
||||||
|
|
||||||
public static void ThrowOperationCanceledException_ReadCanceled() => throw CreateOperationCanceledException_ReadCanceled();
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public static Exception CreateOperationCanceledException_ReadCanceled() => new OperationCanceledException("Read was canceled on underlying PipeReader.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
// 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.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Internal;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a WriteOnlyStream backed by a PipeWriter
|
|
||||||
/// </summary>
|
|
||||||
public class WriteOnlyPipeStream : Stream
|
|
||||||
{
|
|
||||||
private bool _allowSynchronousIO = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new WriteOnlyStream
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pipeWriter">The PipeWriter to write to.</param>
|
|
||||||
public WriteOnlyPipeStream(PipeWriter pipeWriter) :
|
|
||||||
this(pipeWriter, allowSynchronousIO: true)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new WriteOnlyStream
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pipeWriter">The PipeWriter to write to.</param>
|
|
||||||
/// <param name="allowSynchronousIO">Whether synchronous IO is allowed.</param>
|
|
||||||
public WriteOnlyPipeStream(PipeWriter pipeWriter, bool allowSynchronousIO)
|
|
||||||
{
|
|
||||||
InnerPipeWriter = pipeWriter;
|
|
||||||
_allowSynchronousIO = allowSynchronousIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool CanSeek => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool CanRead => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool CanWrite => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override long Length => throw new NotSupportedException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override long Position
|
|
||||||
{
|
|
||||||
get => throw new NotSupportedException();
|
|
||||||
set => throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int ReadTimeout
|
|
||||||
{
|
|
||||||
get => throw new NotSupportedException();
|
|
||||||
set => throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PipeWriter InnerPipeWriter { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
|
||||||
=> throw new NotSupportedException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
=> throw new NotSupportedException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
if (!_allowSynchronousIO)
|
|
||||||
{
|
|
||||||
ThrowHelper.ThrowInvalidOperationException_SynchronousFlushesDisallowed();
|
|
||||||
}
|
|
||||||
|
|
||||||
FlushAsync(default).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return InnerPipeWriter.FlushAsync(cancellationToken).GetAsTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void SetLength(long value)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
if (!_allowSynchronousIO)
|
|
||||||
{
|
|
||||||
ThrowHelper.ThrowInvalidOperationException_SynchronousWritesDisallowed();
|
|
||||||
}
|
|
||||||
WriteAsync(buffer, offset, count, default).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
|
||||||
{
|
|
||||||
var task = WriteAsync(buffer, offset, count, default, state);
|
|
||||||
if (callback != null)
|
|
||||||
{
|
|
||||||
task.ContinueWith(t => callback.Invoke(t));
|
|
||||||
}
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void EndWrite(IAsyncResult asyncResult)
|
|
||||||
{
|
|
||||||
((Task<object>)asyncResult).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
|
|
||||||
{
|
|
||||||
var tcs = new TaskCompletionSource<object>(state);
|
|
||||||
var task = WriteAsync(buffer, offset, count, cancellationToken);
|
|
||||||
task.ContinueWith((task2, state2) =>
|
|
||||||
{
|
|
||||||
var tcs2 = (TaskCompletionSource<object>)state2;
|
|
||||||
if (task2.IsCanceled)
|
|
||||||
{
|
|
||||||
tcs2.SetCanceled();
|
|
||||||
}
|
|
||||||
else if (task2.IsFaulted)
|
|
||||||
{
|
|
||||||
tcs2.SetException(task2.Exception);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tcs2.SetResult(null);
|
|
||||||
}
|
|
||||||
}, tcs, cancellationToken);
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return WriteAsyncInternal(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return new ValueTask(WriteAsyncInternal(source, cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return InnerPipeWriter.WriteAsync(source, cancellationToken).GetAsTask();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -98,7 +98,9 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
await pipe.Writer.WriteAsync(formContent);
|
await pipe.Writer.WriteAsync(formContent);
|
||||||
pipe.Writer.Complete();
|
pipe.Writer.Complete();
|
||||||
|
|
||||||
context.Request.BodyReader = pipe.Reader;
|
var mockFeature = new MockRequestBodyPipeFeature();
|
||||||
|
mockFeature.Reader = pipe.Reader;
|
||||||
|
context.Features.Set<IRequestBodyPipeFeature>(mockFeature);
|
||||||
|
|
||||||
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
|
IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
|
||||||
context.Features.Set<IFormFeature>(formFeature);
|
context.Features.Set<IFormFeature>(formFeature);
|
||||||
|
|
@ -108,16 +110,21 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
Assert.Equal("bar", formCollection["foo"]);
|
Assert.Equal("bar", formCollection["foo"]);
|
||||||
Assert.Equal("2", formCollection["baz"]);
|
Assert.Equal("2", formCollection["baz"]);
|
||||||
|
|
||||||
// Cached
|
// Cached
|
||||||
formFeature = context.Features.Get<IFormFeature>();
|
formFeature = context.Features.Get<IFormFeature>();
|
||||||
Assert.NotNull(formFeature);
|
Assert.NotNull(formFeature);
|
||||||
Assert.NotNull(formFeature.Form);
|
Assert.NotNull(formFeature.Form);
|
||||||
Assert.Same(formFeature.Form, formCollection);
|
Assert.Same(formFeature.Form, formCollection);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
await responseFeature.CompleteAsync();
|
await responseFeature.CompleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class MockRequestBodyPipeFeature : IRequestBodyPipeFeature
|
||||||
|
{
|
||||||
|
public PipeReader Reader { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T";
|
private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T";
|
||||||
|
|
||||||
private const string MultipartContentTypeWithSpecialCharacters = "multipart/form-data; boundary=\"WebKitFormBoundary/:5pDRpGheQXaM8k3T\"";
|
private const string MultipartContentTypeWithSpecialCharacters = "multipart/form-data; boundary=\"WebKitFormBoundary/:5pDRpGheQXaM8k3T\"";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// 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.
|
// 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.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
|
@ -24,78 +23,7 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
|
|
||||||
var pipeBody = feature.Reader;
|
var pipeBody = feature.Reader;
|
||||||
|
|
||||||
Assert.True(pipeBody is StreamPipeReader);
|
Assert.NotNull(pipeBody);
|
||||||
Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RequestBodyReadCanWorkWithPipe()
|
|
||||||
{
|
|
||||||
var expectedString = "abcdef";
|
|
||||||
var feature = InitializeFeatureWithData(expectedString);
|
|
||||||
|
|
||||||
var data = await feature.Reader.ReadAsync();
|
|
||||||
Assert.Equal(expectedString, GetStringFromReadResult(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void RequestBodySetPipeReaderReturnsSameValue()
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
|
|
||||||
var feature = new RequestBodyPipeFeature(context);
|
|
||||||
|
|
||||||
var pipeReader = new Pipe().Reader;
|
|
||||||
feature.Reader = pipeReader;
|
|
||||||
|
|
||||||
Assert.Equal(pipeReader, feature.Reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void RequestBodySetPipeReadReturnsUserSetValueAlways()
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
|
|
||||||
var feature = new RequestBodyPipeFeature(context);
|
|
||||||
|
|
||||||
var expectedPipeReader = new Pipe().Reader;
|
|
||||||
feature.Reader = expectedPipeReader;
|
|
||||||
|
|
||||||
// Because the user set the RequestBodyPipe, this will return the user set pipeReader
|
|
||||||
context.Request.Body = new MemoryStream();
|
|
||||||
|
|
||||||
Assert.Equal(expectedPipeReader, feature.Reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task RequestBodyDoesNotAffectUserSetPipe()
|
|
||||||
{
|
|
||||||
var expectedString = "abcdef";
|
|
||||||
var feature = InitializeFeatureWithData("hahaha");
|
|
||||||
feature.Reader = await GetPipeReaderWithData(expectedString);
|
|
||||||
|
|
||||||
var data = await feature.Reader.ReadAsync();
|
|
||||||
Assert.Equal(expectedString, GetStringFromReadResult(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void RequestBodyGetPipeReaderAfterSettingBodyTwice()
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
|
|
||||||
context.Request.Body = new MemoryStream();
|
|
||||||
|
|
||||||
var feature = new RequestBodyPipeFeature(context);
|
|
||||||
|
|
||||||
var pipeBody = feature.Reader;
|
|
||||||
|
|
||||||
// Requery the PipeReader after setting the body again.
|
|
||||||
var expectedStream = new MemoryStream();
|
|
||||||
context.Request.Body = expectedStream;
|
|
||||||
pipeBody = feature.Reader;
|
|
||||||
|
|
||||||
Assert.True(pipeBody is StreamPipeReader);
|
|
||||||
Assert.Equal(expectedStream, (pipeBody as StreamPipeReader).InnerStream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -112,23 +40,9 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
Assert.Equal(expectedString, GetStringFromReadResult(data));
|
Assert.Equal(expectedString, GetStringFromReadResult(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
private RequestBodyPipeFeature InitializeFeatureWithData(string input)
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
context.Request.Body = new MemoryStream(Encoding.ASCII.GetBytes(input));
|
|
||||||
return new RequestBodyPipeFeature(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetStringFromReadResult(ReadResult data)
|
private static string GetStringFromReadResult(ReadResult data)
|
||||||
{
|
{
|
||||||
return Encoding.ASCII.GetString(data.Buffer.ToArray());
|
return Encoding.ASCII.GetString(data.Buffer.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<PipeReader> GetPipeReaderWithData(string input)
|
|
||||||
{
|
|
||||||
var pipe = new Pipe();
|
|
||||||
await pipe.Writer.WriteAsync(Encoding.ASCII.GetBytes(input));
|
|
||||||
return pipe.Reader;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http.Features
|
namespace Microsoft.AspNetCore.Http.Features
|
||||||
|
|
@ -20,37 +19,7 @@ namespace Microsoft.AspNetCore.Http.Features
|
||||||
|
|
||||||
var pipeBody = feature.Writer;
|
var pipeBody = feature.Writer;
|
||||||
|
|
||||||
Assert.True(pipeBody is StreamPipeWriter);
|
Assert.NotNull(pipeBody);
|
||||||
Assert.Equal(expectedStream, (pipeBody as StreamPipeWriter).InnerStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ResponseBodySetPipeReaderReturnsSameValue()
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
var feature = new ResponseBodyPipeFeature(context);
|
|
||||||
|
|
||||||
var pipeWriter = new Pipe().Writer;
|
|
||||||
feature.Writer = pipeWriter;
|
|
||||||
|
|
||||||
Assert.Equal(pipeWriter, feature.Writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ResponseBodyGetPipeWriterAfterSettingBodyTwice()
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
var expectedStream = new MemoryStream();
|
|
||||||
context.Response.Body = new MemoryStream();
|
|
||||||
|
|
||||||
var feature = new ResponseBodyPipeFeature(context);
|
|
||||||
|
|
||||||
var pipeBody = feature.Writer;
|
|
||||||
context.Response.Body = expectedStream;
|
|
||||||
pipeBody = feature.Writer;
|
|
||||||
|
|
||||||
Assert.True(pipeBody is StreamPipeWriter);
|
|
||||||
Assert.Equal(expectedStream, (pipeBody as StreamPipeWriter).InnerStream);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
// 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.Pipelines;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class FlushResultCancellationTests : StreamPipeTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task FlushAsyncWithNewCancellationTokenNotAffectedByPrevious()
|
|
||||||
{
|
|
||||||
var cancellationTokenSource1 = new CancellationTokenSource();
|
|
||||||
PipeWriter buffer = Writer.WriteEmpty(10);
|
|
||||||
await buffer.FlushAsync(cancellationTokenSource1.Token);
|
|
||||||
|
|
||||||
cancellationTokenSource1.Cancel();
|
|
||||||
|
|
||||||
var cancellationTokenSource2 = new CancellationTokenSource();
|
|
||||||
buffer = Writer.WriteEmpty(10);
|
|
||||||
|
|
||||||
await buffer.FlushAsync(cancellationTokenSource2.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -251,36 +251,6 @@ namespace Microsoft.AspNetCore.Http.Internal
|
||||||
Assert.NotNull(bodyPipe);
|
Assert.NotNull(bodyPipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BodyReader_CanSet()
|
|
||||||
{
|
|
||||||
var pipeReader = new Pipe().Reader;
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
|
|
||||||
context.Request.BodyReader = pipeReader;
|
|
||||||
|
|
||||||
Assert.Equal(pipeReader, context.Request.BodyReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BodyReader_WrapsStream()
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
var expectedStream = new MemoryStream();
|
|
||||||
context.Request.Body = expectedStream;
|
|
||||||
|
|
||||||
var bodyPipe = context.Request.BodyReader as StreamPipeReader;
|
|
||||||
|
|
||||||
Assert.Equal(expectedStream, bodyPipe.InnerStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BodyReader_ThrowsWhenSettingNull()
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
Assert.Throws<ArgumentNullException>(() => context.Request.BodyReader = null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CustomRouteValuesFeature : IRouteValuesFeature
|
private class CustomRouteValuesFeature : IRouteValuesFeature
|
||||||
{
|
{
|
||||||
public RouteValueDictionary RouteValues { get; set; }
|
public RouteValueDictionary RouteValues { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -73,35 +73,6 @@ namespace Microsoft.AspNetCore.Http.Internal
|
||||||
Assert.NotNull(bodyPipe);
|
Assert.NotNull(bodyPipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BodyWriter_CanSet()
|
|
||||||
{
|
|
||||||
var response = new DefaultHttpContext();
|
|
||||||
var pipeWriter = new Pipe().Writer;
|
|
||||||
response.Response.BodyWriter = pipeWriter;
|
|
||||||
|
|
||||||
Assert.Equal(pipeWriter, response.Response.BodyWriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BodyWriter_WrapsStream()
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
var expectedStream = new MemoryStream();
|
|
||||||
context.Response.Body = expectedStream;
|
|
||||||
|
|
||||||
var bodyPipe = context.Response.BodyWriter as StreamPipeWriter;
|
|
||||||
|
|
||||||
Assert.Equal(expectedStream, bodyPipe.InnerStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BodyWriter_ThrowsWhenSettingNull()
|
|
||||||
{
|
|
||||||
var context = new DefaultHttpContext();
|
|
||||||
Assert.Throws<ArgumentNullException>(() => context.Response.BodyWriter = null);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResponseStart_CallsFeatureIfSet()
|
public async Task ResponseStart_CallsFeatureIfSet()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
// 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.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class PipeStreamTest : IDisposable
|
|
||||||
{
|
|
||||||
public Stream ReadingStream { get; set; }
|
|
||||||
public Stream WritingStream { get; set; }
|
|
||||||
|
|
||||||
public Pipe Pipe { get; set; }
|
|
||||||
|
|
||||||
public PipeReader Reader => Pipe.Reader;
|
|
||||||
|
|
||||||
public PipeWriter Writer => Pipe.Writer;
|
|
||||||
|
|
||||||
public PipeStreamTest()
|
|
||||||
{
|
|
||||||
Pipe = new Pipe();
|
|
||||||
ReadingStream = new ReadOnlyPipeStream(Reader);
|
|
||||||
WritingStream = new WriteOnlyPipeStream(Writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Writer.Complete();
|
|
||||||
Reader.Complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WriteStringToStreamAsync(string input)
|
|
||||||
{
|
|
||||||
await WritingStream.WriteAsync(Encoding.ASCII.GetBytes(input));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WriteStringToPipeAsync(string input)
|
|
||||||
{
|
|
||||||
await Writer.WriteAsync(Encoding.ASCII.GetBytes(input));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WriteByteArrayToPipeAsync(byte[] input)
|
|
||||||
{
|
|
||||||
await Writer.WriteAsync(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> ReadFromPipeAsStringAsync()
|
|
||||||
{
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
var result = Encoding.ASCII.GetString(readResult.Buffer.ToArray());
|
|
||||||
Reader.AdvanceTo(readResult.Buffer.End);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> ReadFromStreamAsStringAsync()
|
|
||||||
{
|
|
||||||
var memory = new Memory<byte>(new byte[4096]);
|
|
||||||
var readLength = await ReadingStream.ReadAsync(memory);
|
|
||||||
var result = Encoding.ASCII.GetString(memory.ToArray(), 0, readLength);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<byte[]> ReadFromPipeAsByteArrayAsync()
|
|
||||||
{
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
var result = readResult.Buffer.ToArray();
|
|
||||||
Reader.AdvanceTo(readResult.Buffer.End);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<byte[]> ReadFromStreamAsByteArrayAsync(int size)
|
|
||||||
{
|
|
||||||
return ReadFromStreamAsByteArrayAsync(size, ReadingStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<byte[]> ReadFromStreamAsByteArrayAsync(int size, Stream stream)
|
|
||||||
{
|
|
||||||
var memory = new Memory<byte>(new byte[size]);
|
|
||||||
var readLength = await stream.ReadAsync(memory);
|
|
||||||
return memory.Slice(0, readLength).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,221 +0,0 @@
|
||||||
// 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.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class PipeWriterTests : StreamPipeTest
|
|
||||||
{
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(3, -1, 0)]
|
|
||||||
[InlineData(3, 0, -1)]
|
|
||||||
[InlineData(3, 0, 4)]
|
|
||||||
[InlineData(3, 4, 0)]
|
|
||||||
[InlineData(3, -1, -1)]
|
|
||||||
[InlineData(3, 4, 4)]
|
|
||||||
public void ThrowsForInvalidParameters(int arrayLength, int offset, int length)
|
|
||||||
{
|
|
||||||
var array = new byte[arrayLength];
|
|
||||||
for (var i = 0; i < array.Length; i++)
|
|
||||||
{
|
|
||||||
array[i] = (byte)(i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Writer.Write(new Span<byte>(array, 0, 0));
|
|
||||||
Writer.Write(new Span<byte>(array, array.Length, 0));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Writer.Write(new Span<byte>(array, offset, length));
|
|
||||||
Assert.True(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Assert.True(ex is ArgumentOutOfRangeException);
|
|
||||||
}
|
|
||||||
|
|
||||||
Writer.Write(new Span<byte>(array, 0, array.Length));
|
|
||||||
Assert.Equal(array, Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(0, 3)]
|
|
||||||
[InlineData(1, 2)]
|
|
||||||
[InlineData(2, 1)]
|
|
||||||
[InlineData(1, 1)]
|
|
||||||
public void CanWriteWithOffsetAndLength(int offset, int length)
|
|
||||||
{
|
|
||||||
var array = new byte[] { 1, 2, 3 };
|
|
||||||
|
|
||||||
Writer.Write(new Span<byte>(array, offset, length));
|
|
||||||
|
|
||||||
Assert.Equal(array.Skip(offset).Take(length).ToArray(), Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanWriteIntoHeadlessBuffer()
|
|
||||||
{
|
|
||||||
|
|
||||||
Writer.Write(new byte[] { 1, 2, 3 });
|
|
||||||
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanGetNewMemoryWhenSizeTooLarge()
|
|
||||||
{
|
|
||||||
var memory = Writer.GetMemory(0);
|
|
||||||
|
|
||||||
var memoryLarge = Writer.GetMemory(10000);
|
|
||||||
|
|
||||||
Assert.NotEqual(memory, memoryLarge);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanGetSameMemoryWhenNoAdvance()
|
|
||||||
{
|
|
||||||
var memory = Writer.GetMemory(0);
|
|
||||||
|
|
||||||
var secondMemory = Writer.GetMemory(0);
|
|
||||||
|
|
||||||
Assert.Equal(memory, secondMemory);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(0)]
|
|
||||||
[InlineData(2048)]
|
|
||||||
public void GetSpanWithZeroSizeHintReturnsMaxBufferSizeOfPool(int sizeHint)
|
|
||||||
{
|
|
||||||
var span = Writer.GetSpan(sizeHint);
|
|
||||||
|
|
||||||
Assert.Equal(4096, span.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanGetSameSpanWhenNoAdvance()
|
|
||||||
{
|
|
||||||
var span = Writer.GetSpan(0);
|
|
||||||
|
|
||||||
var secondSpan = Writer.GetSpan(0);
|
|
||||||
|
|
||||||
Assert.True(span.SequenceEqual(secondSpan));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(16, 32, 32)]
|
|
||||||
[InlineData(16, 16, 16)]
|
|
||||||
[InlineData(64, 32, 64)]
|
|
||||||
[InlineData(40, 32, 64)] // memory sizes are powers of 2.
|
|
||||||
public void CheckMinimumSegmentSizeWithGetMemory(int minimumSegmentSize, int getMemorySize, int expectedSize)
|
|
||||||
{
|
|
||||||
var writer = new StreamPipeWriter(new MemoryStream(), minimumSegmentSize);
|
|
||||||
var memory = writer.GetMemory(getMemorySize);
|
|
||||||
|
|
||||||
Assert.Equal(expectedSize, memory.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanWriteMultipleTimes()
|
|
||||||
{
|
|
||||||
|
|
||||||
Writer.Write(new byte[] { 1 });
|
|
||||||
Writer.Write(new byte[] { 2 });
|
|
||||||
Writer.Write(new byte[] { 3 });
|
|
||||||
|
|
||||||
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanWriteOverTheBlockLength()
|
|
||||||
{
|
|
||||||
Memory<byte> memory = Writer.GetMemory();
|
|
||||||
|
|
||||||
IEnumerable<byte> source = Enumerable.Range(0, memory.Length).Select(i => (byte)i);
|
|
||||||
byte[] expectedBytes = source.Concat(source).Concat(source).ToArray();
|
|
||||||
|
|
||||||
Writer.Write(expectedBytes);
|
|
||||||
|
|
||||||
Assert.Equal(expectedBytes, Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void EnsureAllocatesSpan()
|
|
||||||
{
|
|
||||||
var span = Writer.GetSpan(10);
|
|
||||||
|
|
||||||
Assert.True(span.Length >= 10);
|
|
||||||
// 0 byte Flush would not complete the reader so we complete.
|
|
||||||
Writer.Complete();
|
|
||||||
Assert.Equal(new byte[] { }, Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SlicesSpanAndAdvancesAfterWrite()
|
|
||||||
{
|
|
||||||
int initialLength = Writer.GetSpan(3).Length;
|
|
||||||
|
|
||||||
|
|
||||||
Writer.Write(new byte[] { 1, 2, 3 });
|
|
||||||
Span<byte> span = Writer.GetSpan();
|
|
||||||
|
|
||||||
Assert.Equal(initialLength - 3, span.Length);
|
|
||||||
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(5)]
|
|
||||||
[InlineData(50)]
|
|
||||||
[InlineData(500)]
|
|
||||||
[InlineData(5000)]
|
|
||||||
[InlineData(50000)]
|
|
||||||
public async Task WriteLargeDataBinary(int length)
|
|
||||||
{
|
|
||||||
var data = new byte[length];
|
|
||||||
new Random(length).NextBytes(data);
|
|
||||||
PipeWriter output = Writer;
|
|
||||||
output.Write(data);
|
|
||||||
await output.FlushAsync();
|
|
||||||
|
|
||||||
var input = Read();
|
|
||||||
Assert.Equal(data, input.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CanWriteNothingToBuffer()
|
|
||||||
{
|
|
||||||
Writer.GetMemory(0);
|
|
||||||
Writer.Advance(0); // doing nothing, the hard way
|
|
||||||
await Writer.FlushAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void EmptyWriteDoesNotThrow()
|
|
||||||
{
|
|
||||||
Writer.Write(new byte[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ThrowsOnAdvanceOverMemorySize()
|
|
||||||
{
|
|
||||||
Memory<byte> buffer = Writer.GetMemory(1);
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => Writer.Advance(buffer.Length + 1));
|
|
||||||
Assert.Equal("Can't advance past buffer size.", exception.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ThrowsOnAdvanceWithNoMemory()
|
|
||||||
{
|
|
||||||
PipeWriter buffer = Writer;
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => buffer.Advance(1));
|
|
||||||
Assert.Equal("No writing operation. Make sure GetMemory() was called.", exception.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
// 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 System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class ReadAsyncCancellationTests : StreamPipeTest
|
|
||||||
{
|
|
||||||
[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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
// 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.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class ReadOnlyPipeStreamTests : PipeStreamTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void CanSeekFalse()
|
|
||||||
{
|
|
||||||
Assert.False(ReadingStream.CanSeek);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanReadTrue()
|
|
||||||
{
|
|
||||||
Assert.True(ReadingStream.CanRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanWriteFalse()
|
|
||||||
{
|
|
||||||
Assert.False(ReadingStream.CanWrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void LengthThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => ReadingStream.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void PositionThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => ReadingStream.Position);
|
|
||||||
Assert.Throws<NotSupportedException>(() => ReadingStream.Position = 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SeekThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => ReadingStream.Seek(0, SeekOrigin.Begin));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SetLengthThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => ReadingStream.SetLength(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void WriteThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => ReadingStream.Write(new byte[1], 0, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteAsyncThrows()
|
|
||||||
{
|
|
||||||
await Assert.ThrowsAsync<NotSupportedException>(async () => await ReadingStream.WriteAsync(new byte[1], 0, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ReadTimeoutThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => ReadingStream.WriteTimeout = 1);
|
|
||||||
Assert.Throws<NotSupportedException>(() => ReadingStream.WriteTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadAsyncWorks()
|
|
||||||
{
|
|
||||||
var expected = "Hello World!";
|
|
||||||
|
|
||||||
await WriteStringToPipeAsync(expected);
|
|
||||||
|
|
||||||
Assert.Equal(expected, await ReadFromStreamAsStringAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task BasicLargeRead()
|
|
||||||
{
|
|
||||||
var expected = new byte[8000];
|
|
||||||
|
|
||||||
await WriteByteArrayToPipeAsync(expected);
|
|
||||||
|
|
||||||
Assert.Equal(expected, await ReadFromStreamAsByteArrayAsync(8000));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadAsyncIsCalledFromCallingRead()
|
|
||||||
{
|
|
||||||
var pipeReader = await SetupMockPipeReader();
|
|
||||||
var stream = new ReadOnlyPipeStream(pipeReader.Object);
|
|
||||||
|
|
||||||
stream.Read(new byte[1]);
|
|
||||||
|
|
||||||
pipeReader.Verify(m => m.ReadAsync(It.IsAny<CancellationToken>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadAsyncIsCalledFromCallingReadAsync()
|
|
||||||
{
|
|
||||||
var pipeReader = await SetupMockPipeReader();
|
|
||||||
var stream = new ReadOnlyPipeStream(pipeReader.Object);
|
|
||||||
|
|
||||||
await stream.ReadAsync(new byte[1]);
|
|
||||||
|
|
||||||
pipeReader.Verify(m => m.ReadAsync(It.IsAny<CancellationToken>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadAsyncCancellationTokenIsPassedIntoReadAsync()
|
|
||||||
{
|
|
||||||
var pipeReader = await SetupMockPipeReader();
|
|
||||||
var stream = new ReadOnlyPipeStream(pipeReader.Object);
|
|
||||||
var token = new CancellationToken();
|
|
||||||
|
|
||||||
await stream.ReadAsync(new byte[1], token);
|
|
||||||
|
|
||||||
pipeReader.Verify(m => m.ReadAsync(token));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CopyToAsyncWorks()
|
|
||||||
{
|
|
||||||
const int expectedSize = 8000;
|
|
||||||
var expected = new byte[expectedSize];
|
|
||||||
|
|
||||||
await WriteByteArrayToPipeAsync(expected);
|
|
||||||
|
|
||||||
Writer.Complete();
|
|
||||||
var destStream = new MemoryStream();
|
|
||||||
|
|
||||||
await ReadingStream.CopyToAsync(destStream);
|
|
||||||
|
|
||||||
Assert.Equal(expectedSize, destStream.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BlockSyncIOThrows()
|
|
||||||
{
|
|
||||||
var readOnlyPipeStream = new ReadOnlyPipeStream(Reader, allowSynchronousIO: false);
|
|
||||||
Assert.Throws<InvalidOperationException>(() => readOnlyPipeStream.Read(new byte[0], 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void InnerPipeReaderReturnsPipeReader()
|
|
||||||
{
|
|
||||||
var readOnlyPipeStream = new ReadOnlyPipeStream(Reader, allowSynchronousIO: false);
|
|
||||||
Assert.Equal(Reader, readOnlyPipeStream.InnerPipeReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ThrowsOperationCanceledExceptionIfCancelPendingReadWasCalledOnInnerPipeReader()
|
|
||||||
{
|
|
||||||
var readOnlyPipeStream = new ReadOnlyPipeStream(Reader);
|
|
||||||
var readOperation = readOnlyPipeStream.ReadAsync(new byte[1]);
|
|
||||||
|
|
||||||
Assert.False(readOperation.IsCompleted);
|
|
||||||
|
|
||||||
Reader.CancelPendingRead();
|
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<OperationCanceledException>(async () => await readOperation);
|
|
||||||
|
|
||||||
Assert.Equal(ThrowHelper.CreateOperationCanceledException_ReadCanceled().Message, ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Mock<PipeReader>> SetupMockPipeReader()
|
|
||||||
{
|
|
||||||
await WriteByteArrayToPipeAsync(new byte[1]);
|
|
||||||
|
|
||||||
var pipeReader = new Mock<PipeReader>();
|
|
||||||
pipeReader
|
|
||||||
.Setup(m => m.ReadAsync(It.IsAny<CancellationToken>()))
|
|
||||||
.Returns(new ValueTask<ReadResult>(new ReadResult(new ReadOnlySequence<byte>(new byte[1]), false, false)));
|
|
||||||
return pipeReader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
// 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.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class ReadingAdaptersInteropTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task CheckBasicReadPipeApi()
|
|
||||||
{
|
|
||||||
var pipe = new Pipe();
|
|
||||||
var readStream = new ReadOnlyPipeStream(pipe.Reader);
|
|
||||||
var pipeReader = new StreamPipeReader(readStream);
|
|
||||||
|
|
||||||
await pipe.Writer.WriteAsync(new byte[10]);
|
|
||||||
var res = await pipeReader.ReadAsync();
|
|
||||||
Assert.Equal(new byte[10], res.Buffer.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CheckNestedPipeApi()
|
|
||||||
{
|
|
||||||
var pipe = new Pipe();
|
|
||||||
var reader = pipe.Reader;
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
var readStream = new ReadOnlyPipeStream(reader);
|
|
||||||
reader = new StreamPipeReader(readStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
await pipe.Writer.WriteAsync(new byte[10]);
|
|
||||||
var res = await reader.ReadAsync();
|
|
||||||
Assert.Equal(new byte[10], res.Buffer.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CheckBasicReadStreamApi()
|
|
||||||
{
|
|
||||||
var stream = new MemoryStream();
|
|
||||||
await stream.WriteAsync(new byte[10]);
|
|
||||||
stream.Position = 0;
|
|
||||||
|
|
||||||
var pipeReader = new StreamPipeReader(stream);
|
|
||||||
var readOnlyStream = new ReadOnlyPipeStream(pipeReader);
|
|
||||||
|
|
||||||
var resSize = await readOnlyStream.ReadAsync(new byte[10]);
|
|
||||||
|
|
||||||
Assert.Equal(10, resSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CheckNestedStreamApi()
|
|
||||||
{
|
|
||||||
var stream = new MemoryStream();
|
|
||||||
await stream.WriteAsync(new byte[10]);
|
|
||||||
stream.Position = 0;
|
|
||||||
|
|
||||||
Stream readOnlyStream = stream;
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
var pipeReader = new StreamPipeReader(readOnlyStream);
|
|
||||||
readOnlyStream = new ReadOnlyPipeStream(pipeReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
var resSize = await readOnlyStream.ReadAsync(new byte[10]);
|
|
||||||
|
|
||||||
Assert.Equal(10, resSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadsCanBeCanceledViaProvidedCancellationToken()
|
|
||||||
{
|
|
||||||
var readOnlyStream = new ReadOnlyPipeStream(new HangingPipeReader());
|
|
||||||
var pipeReader = new StreamPipeReader(readOnlyStream);
|
|
||||||
|
|
||||||
var cts = new CancellationTokenSource(1);
|
|
||||||
await Task.Delay(1);
|
|
||||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await pipeReader.ReadAsync(cts.Token));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadCanBeCancelledViaCancelPendingReadWhenReadIsAsync()
|
|
||||||
{
|
|
||||||
var readOnlyStream = new ReadOnlyPipeStream(new HangingPipeReader());
|
|
||||||
var pipeReader = new StreamPipeReader(readOnlyStream);
|
|
||||||
|
|
||||||
var result = new ReadResult();
|
|
||||||
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
var task = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var readingTask = pipeReader.ReadAsync();
|
|
||||||
tcs.SetResult(0);
|
|
||||||
result = await readingTask;
|
|
||||||
});
|
|
||||||
await tcs.Task;
|
|
||||||
pipeReader.CancelPendingRead();
|
|
||||||
await task;
|
|
||||||
|
|
||||||
Assert.True(result.IsCanceled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class HangingPipeReader : PipeReader
|
|
||||||
{
|
|
||||||
public override void AdvanceTo(SequencePosition consumed)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CancelPendingRead()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Complete(Exception exception = null)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnWriterCompleted(Action<Exception, object> callback, object state)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await Task.Delay(30000, cancellationToken);
|
|
||||||
return new ReadResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool TryRead(out ReadResult result)
|
|
||||||
{
|
|
||||||
result = new ReadResult();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,752 +0,0 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public partial class StreamPipeReaderTests : StreamPipeTest
|
|
||||||
{
|
|
||||||
[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()
|
|
||||||
{
|
|
||||||
WriteByteArray(9000);
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
CreateReader(minimumSegmentSize: 4095);
|
|
||||||
|
|
||||||
WriteByteArray(9000);
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
CreateReader();
|
|
||||||
|
|
||||||
WriteByteArray(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 readingTask = pipeReader.ReadAsync();
|
|
||||||
tcs.SetResult(0);
|
|
||||||
result = await readingTask;
|
|
||||||
});
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
Write(new byte[10000]);
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
{
|
|
||||||
CreateReader();
|
|
||||||
|
|
||||||
WriteByteArray(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()
|
|
||||||
{
|
|
||||||
CreateReader();
|
|
||||||
|
|
||||||
WriteByteArray(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()
|
|
||||||
{
|
|
||||||
WriteByteArray(100);
|
|
||||||
await Reader.ReadAsync();
|
|
||||||
Reader.Complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task AdvanceAfterCompleteThrows()
|
|
||||||
{
|
|
||||||
WriteByteArray(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;
|
|
||||||
CreateReader();
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
WriteByteArray(20);
|
|
||||||
var task = Reader.ReadAsync();
|
|
||||||
Assert.True(IsTaskWithResult(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ArrayPoolUsedByDefault()
|
|
||||||
{
|
|
||||||
WriteByteArray(20);
|
|
||||||
var reader = new StreamPipeReader(Stream);
|
|
||||||
var result = await reader.ReadAsync();
|
|
||||||
|
|
||||||
SequenceMarshal.TryGetReadOnlySequenceSegment(
|
|
||||||
result.Buffer,
|
|
||||||
out var startSegment,
|
|
||||||
out var startIndex,
|
|
||||||
out var endSegment,
|
|
||||||
out var endIndex);
|
|
||||||
|
|
||||||
var start = (BufferSegment)startSegment;
|
|
||||||
var end = (BufferSegment)endSegment;
|
|
||||||
|
|
||||||
Assert.Same(start, end);
|
|
||||||
Assert.IsType<byte[]>(start.MemoryOwner);
|
|
||||||
|
|
||||||
reader.AdvanceTo(result.Buffer.End);
|
|
||||||
reader.Complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CancelledReadAsyncReturnsTaskWithValue()
|
|
||||||
{
|
|
||||||
Reader.CancelPendingRead();
|
|
||||||
var task = Reader.ReadAsync();
|
|
||||||
Assert.True(IsTaskWithResult(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task AdvancePastMinReadSizeReadAsyncReturnsMoreData()
|
|
||||||
{
|
|
||||||
CreateReader();
|
|
||||||
|
|
||||||
WriteByteArray(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()
|
|
||||||
{
|
|
||||||
WriteByteArray(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 pool = new TestMemoryPool();
|
|
||||||
CreateReader(memoryPool: pool);
|
|
||||||
|
|
||||||
WriteByteArray(2000);
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
Stream = new AsyncStream();
|
|
||||||
CreateReader();
|
|
||||||
|
|
||||||
WriteByteArray(2000);
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
CreateReader();
|
|
||||||
|
|
||||||
Write(Encoding.ASCII.GetBytes(new string('a', 8)));
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
Reader.AdvanceTo(readResult.Buffer.GetPosition(4), readResult.Buffer.End);
|
|
||||||
Stream.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()
|
|
||||||
{
|
|
||||||
CreateReader();
|
|
||||||
|
|
||||||
Write(Encoding.ASCII.GetBytes(new string('a', 8)));
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
Reader.AdvanceTo(readResult.Buffer.GetPosition(4), readResult.Buffer.End);
|
|
||||||
Stream.Position = 0;
|
|
||||||
|
|
||||||
readResult = await Reader.ReadAsync();
|
|
||||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
|
||||||
Stream.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task SetMinimumReadThresholdSegmentAdvancesCorrectly()
|
|
||||||
{
|
|
||||||
CreateReader(minimumReadThreshold: 8);
|
|
||||||
|
|
||||||
WriteByteArray(9);
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
|
|
||||||
|
|
||||||
AppendByteArray(9);
|
|
||||||
readResult = await Reader.ReadAsync();
|
|
||||||
|
|
||||||
foreach (var segment in readResult.Buffer)
|
|
||||||
{
|
|
||||||
Assert.Equal(9, segment.Length);
|
|
||||||
}
|
|
||||||
Assert.False(readResult.Buffer.IsSingleSegment);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SetMinimumReadThresholdOfZeroThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<ArgumentOutOfRangeException>(() => new StreamPipeReader(Stream,
|
|
||||||
new StreamPipeReaderAdapterOptions(minimumSegmentSize: 4096, minimumReadThreshold: 0, new TestMemoryPool())));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SetOptionsToNullThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<ArgumentNullException>(() => new StreamPipeReader(Stream, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UseBothStreamAndPipeToReadConfirmSameSize()
|
|
||||||
{
|
|
||||||
Write(new byte[8]);
|
|
||||||
var buffer = new byte[4];
|
|
||||||
|
|
||||||
Stream.Read(buffer, 0, buffer.Length);
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
|
|
||||||
Assert.Equal(buffer, readResult.Buffer.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UseStreamThenPipeToReadNoBytesLost()
|
|
||||||
{
|
|
||||||
CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1);
|
|
||||||
|
|
||||||
var expectedString = WriteString("abcdef");
|
|
||||||
var accumulatedResult = "";
|
|
||||||
var buffer = new byte[1];
|
|
||||||
|
|
||||||
for (var i = 0; i < expectedString.Length / 2; i++)
|
|
||||||
{
|
|
||||||
// Read from stream then pipe to guarantee no bytes are lost.
|
|
||||||
accumulatedResult += ReadFromStreamAsString(buffer);
|
|
||||||
accumulatedResult += await ReadFromPipeAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(expectedString, accumulatedResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UsePipeThenStreamToReadNoBytesLost()
|
|
||||||
{
|
|
||||||
CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1);
|
|
||||||
|
|
||||||
var expectedString = WriteString("abcdef");
|
|
||||||
var accumulatedResult = "";
|
|
||||||
var buffer = new byte[1];
|
|
||||||
|
|
||||||
for (var i = 0; i < expectedString.Length / 2; i++)
|
|
||||||
{
|
|
||||||
// Read from pipe then stream to guarantee no bytes are lost.
|
|
||||||
accumulatedResult += await ReadFromPipeAsString();
|
|
||||||
accumulatedResult += ReadFromStreamAsString(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(expectedString, accumulatedResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UseBothStreamAndPipeToReadWithoutAdvance_StreamIgnoresAdvance()
|
|
||||||
{
|
|
||||||
var buffer = new byte[1];
|
|
||||||
CreateReader(minimumSegmentSize: 1, minimumReadThreshold: 1);
|
|
||||||
|
|
||||||
WriteString("abc");
|
|
||||||
ReadFromStreamAsString(buffer);
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
|
|
||||||
// No Advance
|
|
||||||
// Next call to Stream.Read will get the next 4 bytes rather than the bytes already read by the pipe
|
|
||||||
Assert.Equal("c", ReadFromStreamAsString(buffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadAsyncWithNoDataCompletesReader()
|
|
||||||
{
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
|
|
||||||
Assert.True(readResult.IsCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadAsyncWithEmptyDataCompletesStream()
|
|
||||||
{
|
|
||||||
WriteByteArray(0);
|
|
||||||
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
|
|
||||||
Assert.True(readResult.IsCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadAsyncAfterReceivingCompletedReadResultDoesNotThrow()
|
|
||||||
{
|
|
||||||
Stream = new ThrowAfterZeroByteReadStream();
|
|
||||||
Reader = new StreamPipeReader(Stream);
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
|
|
||||||
readResult = await Reader.ReadAsync();
|
|
||||||
Assert.True(readResult.Buffer.IsEmpty);
|
|
||||||
Assert.True(readResult.IsCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void InnerStreamReturnsStream()
|
|
||||||
{
|
|
||||||
Assert.Equal(Stream, ((StreamPipeReader)Reader).InnerStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task BufferingDataPastEndOfStreamCanBeReadAgain()
|
|
||||||
{
|
|
||||||
var helloBytes = Encoding.ASCII.GetBytes("Hello World");
|
|
||||||
Write(helloBytes);
|
|
||||||
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
var buffer = readResult.Buffer;
|
|
||||||
Reader.AdvanceTo(buffer.Start, buffer.End);
|
|
||||||
|
|
||||||
// Make sure IsCompleted is true
|
|
||||||
readResult = await Reader.ReadAsync();
|
|
||||||
buffer = readResult.Buffer;
|
|
||||||
Reader.AdvanceTo(buffer.Start, buffer.End);
|
|
||||||
Assert.True(readResult.IsCompleted);
|
|
||||||
|
|
||||||
var value = await ReadFromPipeAsString();
|
|
||||||
Assert.Equal("Hello World", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> ReadFromPipeAsString()
|
|
||||||
{
|
|
||||||
var readResult = await Reader.ReadAsync();
|
|
||||||
|
|
||||||
var result = Encoding.ASCII.GetString(readResult.Buffer.ToArray());
|
|
||||||
Reader.AdvanceTo(readResult.Buffer.End);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ReadFromStreamAsString(byte[] buffer)
|
|
||||||
{
|
|
||||||
var res = Stream.Read(buffer, 0, buffer.Length);
|
|
||||||
return Encoding.ASCII.GetString(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string WriteString(string expectedString)
|
|
||||||
{
|
|
||||||
Write(Encoding.ASCII.GetBytes(expectedString));
|
|
||||||
return expectedString;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateReader(int minimumSegmentSize = 16, int minimumReadThreshold = 4, MemoryPool<byte> memoryPool = null)
|
|
||||||
{
|
|
||||||
Reader = new StreamPipeReader(Stream,
|
|
||||||
new StreamPipeReaderAdapterOptions(
|
|
||||||
minimumSegmentSize,
|
|
||||||
minimumReadThreshold,
|
|
||||||
memoryPool ?? new TestMemoryPool()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsTaskWithResult<T>(ValueTask<T> task)
|
|
||||||
{
|
|
||||||
return task == new ValueTask<T>(task.Result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteByteArray(int size)
|
|
||||||
{
|
|
||||||
Write(new byte[size]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AppendByteArray(int size)
|
|
||||||
{
|
|
||||||
Append(new byte[size]);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keeping as this code will eventually be ported to corefx
|
|
||||||
#if NETCOREAPP3_0
|
|
||||||
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await Task.Yield();
|
|
||||||
return await base.ReadAsync(buffer, cancellationToken);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ThrowAfterZeroByteReadStream : MemoryStream
|
|
||||||
{
|
|
||||||
private bool _throwOnNextCallToRead;
|
|
||||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return ReadAsync(new Memory<byte>(buffer, offset, count)).AsTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (_throwOnNextCallToRead)
|
|
||||||
{
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
var bytes = await base.ReadAsync(destination, cancellationToken);
|
|
||||||
if (bytes == 0)
|
|
||||||
{
|
|
||||||
_throwOnNextCallToRead = true;
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
// 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.Text;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public abstract class StreamPipeTest : IDisposable
|
|
||||||
{
|
|
||||||
protected const int MaximumSizeHigh = 65;
|
|
||||||
|
|
||||||
protected const int MinimumSegmentSize = 4096;
|
|
||||||
|
|
||||||
public Stream Stream { get; set; }
|
|
||||||
|
|
||||||
public PipeWriter Writer { get; set; }
|
|
||||||
|
|
||||||
public PipeReader Reader { get; set; }
|
|
||||||
|
|
||||||
public TestMemoryPool Pool { get; set; }
|
|
||||||
|
|
||||||
protected StreamPipeTest()
|
|
||||||
{
|
|
||||||
Pool = new TestMemoryPool();
|
|
||||||
Stream = new MemoryStream();
|
|
||||||
Writer = new StreamPipeWriter(Stream, MinimumSegmentSize, Pool);
|
|
||||||
Reader = new StreamPipeReader(Stream, new StreamPipeReaderAdapterOptions(MinimumSegmentSize, minimumReadThreshold: 256, Pool));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Writer.Complete();
|
|
||||||
Reader.Complete();
|
|
||||||
Pool.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Read()
|
|
||||||
{
|
|
||||||
Writer.FlushAsync().GetAwaiter().GetResult();
|
|
||||||
return ReadWithoutFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ReadAsString()
|
|
||||||
{
|
|
||||||
Writer.FlushAsync().GetAwaiter().GetResult();
|
|
||||||
return Encoding.ASCII.GetString(ReadWithoutFlush());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write(byte[] data)
|
|
||||||
{
|
|
||||||
Stream.Write(data, 0, data.Length);
|
|
||||||
Stream.Position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteWithoutPosition(byte[] data)
|
|
||||||
{
|
|
||||||
Stream.Write(data, 0, data.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Append(byte[] data)
|
|
||||||
{
|
|
||||||
var originalPosition = Stream.Position;
|
|
||||||
Stream.Write(data, 0, data.Length);
|
|
||||||
Stream.Position = originalPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ReadWithoutFlush()
|
|
||||||
{
|
|
||||||
Stream.Position = 0;
|
|
||||||
var buffer = new byte[Stream.Length];
|
|
||||||
var result = Stream.Read(buffer, 0, (int)Stream.Length);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,556 +0,0 @@
|
||||||
// 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;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class StreamPipeWriterTests : StreamPipeTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task CanWriteAsyncMultipleTimesIntoSameBlock()
|
|
||||||
{
|
|
||||||
await Writer.WriteAsync(new byte[] { 1 });
|
|
||||||
await Writer.WriteAsync(new byte[] { 2 });
|
|
||||||
await Writer.WriteAsync(new byte[] { 3 });
|
|
||||||
|
|
||||||
Assert.Equal(new byte[] { 1, 2, 3 }, Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(100)]
|
|
||||||
[InlineData(4000)]
|
|
||||||
public async Task CanAdvanceWithPartialConsumptionOfFirstSegment(int firstWriteLength)
|
|
||||||
{
|
|
||||||
Writer = new StreamPipeWriter(Stream, MinimumSegmentSize, new TestMemoryPool(maxBufferSize: 20000));
|
|
||||||
await Writer.WriteAsync(Encoding.ASCII.GetBytes("a"));
|
|
||||||
|
|
||||||
var memory = Writer.GetMemory(firstWriteLength);
|
|
||||||
Writer.Advance(firstWriteLength);
|
|
||||||
|
|
||||||
memory = Writer.GetMemory();
|
|
||||||
Writer.Advance(memory.Length);
|
|
||||||
|
|
||||||
await Writer.FlushAsync();
|
|
||||||
|
|
||||||
Assert.Equal(firstWriteLength + memory.Length + 1, Read().Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteCanBeCancelledViaProvidedCancellationToken()
|
|
||||||
{
|
|
||||||
var pipeWriter = new StreamPipeWriter(new HangingStream());
|
|
||||||
var cts = new CancellationTokenSource(1);
|
|
||||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await pipeWriter.WriteAsync(Encoding.ASCII.GetBytes("data"), cts.Token));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteCanBeCanceledViaCancelPendingFlushWhenFlushIsAsync()
|
|
||||||
{
|
|
||||||
var pipeWriter = new StreamPipeWriter(new HangingStream());
|
|
||||||
FlushResult flushResult = new FlushResult();
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
var task = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var writingTask = pipeWriter.WriteAsync(Encoding.ASCII.GetBytes("data"));
|
|
||||||
tcs.SetResult(0);
|
|
||||||
flushResult = await writingTask;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex.Message);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await tcs.Task;
|
|
||||||
|
|
||||||
pipeWriter.CancelPendingFlush();
|
|
||||||
|
|
||||||
await task;
|
|
||||||
|
|
||||||
Assert.True(flushResult.IsCanceled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FlushAsyncCompletedAfterPreCancellation()
|
|
||||||
{
|
|
||||||
PipeWriter writableBuffer = Writer.WriteEmpty(1);
|
|
||||||
|
|
||||||
Writer.CancelPendingFlush();
|
|
||||||
|
|
||||||
ValueTask<FlushResult> flushAsync = writableBuffer.FlushAsync();
|
|
||||||
|
|
||||||
Assert.True(flushAsync.IsCompleted);
|
|
||||||
|
|
||||||
FlushResult flushResult = flushAsync.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
Assert.True(flushResult.IsCanceled);
|
|
||||||
|
|
||||||
flushAsync = writableBuffer.FlushAsync();
|
|
||||||
|
|
||||||
Assert.True(flushAsync.IsCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task FlushAsyncReturnsCanceledIfCanceledBeforeFlush()
|
|
||||||
{
|
|
||||||
await CheckCanceledFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task FlushAsyncReturnsCanceledIfCanceledBeforeFlushMultipleTimes()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
await CheckCanceledFlush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task FlushAsyncReturnsCanceledInterleaved()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 5; i++)
|
|
||||||
{
|
|
||||||
await CheckCanceledFlush();
|
|
||||||
await CheckWriteIsNotCanceled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CancelPendingFlushBetweenWritesAllDataIsPreserved()
|
|
||||||
{
|
|
||||||
Stream = new SingleWriteStream();
|
|
||||||
Writer = new StreamPipeWriter(Stream);
|
|
||||||
FlushResult flushResult = new FlushResult();
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
var task = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Writer.WriteAsync(Encoding.ASCII.GetBytes("data"));
|
|
||||||
|
|
||||||
var writingTask = Writer.WriteAsync(Encoding.ASCII.GetBytes(" data"));
|
|
||||||
tcs.SetResult(0);
|
|
||||||
flushResult = await writingTask;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex.Message);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await tcs.Task;
|
|
||||||
|
|
||||||
Writer.CancelPendingFlush();
|
|
||||||
|
|
||||||
await task;
|
|
||||||
|
|
||||||
Assert.True(flushResult.IsCanceled);
|
|
||||||
|
|
||||||
await Writer.WriteAsync(Encoding.ASCII.GetBytes(" more data"));
|
|
||||||
Assert.Equal(Encoding.ASCII.GetBytes("data data more data"), Read());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CancelPendingFlushAfterAllWritesAllDataIsPreserved()
|
|
||||||
{
|
|
||||||
Stream = new CannotFlushStream();
|
|
||||||
Writer = new StreamPipeWriter(Stream);
|
|
||||||
FlushResult flushResult = new FlushResult();
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
var task = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Create two Segments
|
|
||||||
// First one will succeed to write, other one will hang.
|
|
||||||
var writingTask = Writer.WriteAsync(Encoding.ASCII.GetBytes("data"));
|
|
||||||
tcs.SetResult(0);
|
|
||||||
flushResult = await writingTask;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex.Message);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await tcs.Task;
|
|
||||||
|
|
||||||
Writer.CancelPendingFlush();
|
|
||||||
|
|
||||||
await task;
|
|
||||||
|
|
||||||
Assert.True(flushResult.IsCanceled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CancelPendingFlushLostOfCancellationsNoDataLost()
|
|
||||||
{
|
|
||||||
var writeSize = 16;
|
|
||||||
var singleWriteStream = new SingleWriteStream();
|
|
||||||
Stream = singleWriteStream;
|
|
||||||
Writer = new StreamPipeWriter(Stream, minimumSegmentSize: writeSize);
|
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
FlushResult flushResult = new FlushResult();
|
|
||||||
var expectedData = Encoding.ASCII.GetBytes(new string('a', writeSize));
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
var task = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Create two Segments
|
|
||||||
// First one will succeed to write, other one will hang.
|
|
||||||
for (var j = 0; j < 2; j++)
|
|
||||||
{
|
|
||||||
Writer.Write(expectedData);
|
|
||||||
}
|
|
||||||
|
|
||||||
var flushTask = Writer.FlushAsync();
|
|
||||||
tcs.SetResult(0);
|
|
||||||
flushResult = await flushTask;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex.Message);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await tcs.Task;
|
|
||||||
|
|
||||||
Writer.CancelPendingFlush();
|
|
||||||
|
|
||||||
await task;
|
|
||||||
|
|
||||||
Assert.True(flushResult.IsCanceled);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only half of the data was written because every other flush failed.
|
|
||||||
Assert.Equal(16 * 10, ReadWithoutFlush().Length);
|
|
||||||
|
|
||||||
// Start allowing all writes to make read succeed.
|
|
||||||
singleWriteStream.AllowAllWrites = true;
|
|
||||||
|
|
||||||
Assert.Equal(16 * 10 * 2, Read().Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UseBothStreamAndPipeToWrite()
|
|
||||||
{
|
|
||||||
await WriteStringToPipeWriter("a");
|
|
||||||
WriteStringToStream("c");
|
|
||||||
|
|
||||||
Assert.Equal("ac", ReadAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UsePipeThenStreamToWriteMultipleTimes()
|
|
||||||
{
|
|
||||||
var expectedString = "abcdef";
|
|
||||||
for (var i = 0; i < expectedString.Length; i++)
|
|
||||||
{
|
|
||||||
if (i % 2 == 0)
|
|
||||||
{
|
|
||||||
WriteStringToStream(expectedString[i].ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await WriteStringToPipeWriter(expectedString[i].ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(expectedString, ReadAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task UseStreamThenPipeToWriteMultipleTimes()
|
|
||||||
{
|
|
||||||
var expectedString = "abcdef";
|
|
||||||
for (var i = 0; i < expectedString.Length; i++)
|
|
||||||
{
|
|
||||||
if (i % 2 == 0)
|
|
||||||
{
|
|
||||||
await WriteStringToPipeWriter(expectedString[i].ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
WriteStringToStream(expectedString[i].ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(expectedString, ReadAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CallCompleteWithoutFlush_ThrowsInvalidOperationException()
|
|
||||||
{
|
|
||||||
var memory = Writer.GetMemory();
|
|
||||||
Writer.Advance(memory.Length);
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() => Writer.Complete());
|
|
||||||
Assert.Equal(ThrowHelper.CreateInvalidOperationException_DataNotAllFlushed().Message, ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CallCompleteWithoutFlushAndException_DoesNotThrowInvalidOperationException()
|
|
||||||
{
|
|
||||||
var memory = Writer.GetMemory();
|
|
||||||
Writer.Advance(memory.Length);
|
|
||||||
Writer.Complete(new Exception());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetMemorySameAsTheMaxPoolSizeUsesThePool()
|
|
||||||
{
|
|
||||||
var memory = Writer.GetMemory(Pool.MaxBufferSize);
|
|
||||||
|
|
||||||
Assert.Equal(Pool.MaxBufferSize, memory.Length);
|
|
||||||
Assert.Equal(1, Pool.GetRentCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetMemoryBiggerThanPoolSizeAllocatesUnpooledArray()
|
|
||||||
{
|
|
||||||
var memory = Writer.GetMemory(Pool.MaxBufferSize + 1);
|
|
||||||
|
|
||||||
Assert.Equal(Pool.MaxBufferSize + 1, memory.Length);
|
|
||||||
Assert.Equal(0, Pool.GetRentCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CallComplete_GetMemoryThrows()
|
|
||||||
{
|
|
||||||
Writer.Complete();
|
|
||||||
Assert.Throws<InvalidOperationException>(() => Writer.GetMemory());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CallComplete_GetSpanThrows()
|
|
||||||
{
|
|
||||||
Writer.Complete();
|
|
||||||
Assert.Throws<InvalidOperationException>(() => Writer.GetSpan());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void DisposeDoesNotThrowIfUnflushedData()
|
|
||||||
{
|
|
||||||
var streamPipeWriter = new StreamPipeWriter(new MemoryStream());
|
|
||||||
streamPipeWriter.Write(new byte[1]);
|
|
||||||
|
|
||||||
streamPipeWriter.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CompleteAfterDisposeDoesNotThrowIfUnflushedData()
|
|
||||||
{
|
|
||||||
var streamPipeWriter = new StreamPipeWriter(new MemoryStream());
|
|
||||||
streamPipeWriter.Write(new byte[1]);
|
|
||||||
|
|
||||||
streamPipeWriter.Dispose();
|
|
||||||
streamPipeWriter.Complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CallGetMemoryWithNegativeSizeHint_ThrowsArgException()
|
|
||||||
{
|
|
||||||
Assert.Throws<ArgumentOutOfRangeException>(() => Writer.GetMemory(-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CallGetSpanWithNegativeSizeHint_ThrowsArgException()
|
|
||||||
{
|
|
||||||
Assert.Throws<ArgumentOutOfRangeException>(() => Writer.GetSpan(-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetMemorySlicesCorrectly()
|
|
||||||
{
|
|
||||||
var expectedString = "abcdef";
|
|
||||||
var memory = Writer.GetMemory();
|
|
||||||
|
|
||||||
Encoding.ASCII.GetBytes("abc").CopyTo(memory);
|
|
||||||
Writer.Advance(3);
|
|
||||||
memory = Writer.GetMemory();
|
|
||||||
Encoding.ASCII.GetBytes("def").CopyTo(memory);
|
|
||||||
Writer.Advance(3);
|
|
||||||
|
|
||||||
await Writer.FlushAsync();
|
|
||||||
Assert.Equal(expectedString, ReadAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetSpanSlicesCorrectly()
|
|
||||||
{
|
|
||||||
var expectedString = "abcdef";
|
|
||||||
|
|
||||||
void NonAsyncMethod()
|
|
||||||
{
|
|
||||||
var span = Writer.GetSpan();
|
|
||||||
|
|
||||||
Encoding.ASCII.GetBytes("abc").CopyTo(span);
|
|
||||||
Writer.Advance(3);
|
|
||||||
span = Writer.GetSpan();
|
|
||||||
Encoding.ASCII.GetBytes("def").CopyTo(span);
|
|
||||||
Writer.Advance(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
NonAsyncMethod();
|
|
||||||
|
|
||||||
await Writer.FlushAsync();
|
|
||||||
Assert.Equal(expectedString, ReadAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void InnerStreamReturnsStream()
|
|
||||||
{
|
|
||||||
Assert.Equal(Stream, ((StreamPipeWriter)Writer).InnerStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteStringToStream(string input)
|
|
||||||
{
|
|
||||||
var buffer = Encoding.ASCII.GetBytes(input);
|
|
||||||
Stream.Write(buffer, 0, buffer.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task WriteStringToPipeWriter(string input)
|
|
||||||
{
|
|
||||||
await Writer.WriteAsync(Encoding.ASCII.GetBytes(input));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CheckWriteIsNotCanceled()
|
|
||||||
{
|
|
||||||
var flushResult = await Writer.WriteAsync(Encoding.ASCII.GetBytes("data"));
|
|
||||||
Assert.False(flushResult.IsCanceled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CheckCanceledFlush()
|
|
||||||
{
|
|
||||||
PipeWriter writableBuffer = Writer.WriteEmpty(MaximumSizeHigh);
|
|
||||||
|
|
||||||
Writer.CancelPendingFlush();
|
|
||||||
|
|
||||||
ValueTask<FlushResult> flushAsync = writableBuffer.FlushAsync();
|
|
||||||
|
|
||||||
Assert.True(flushAsync.IsCompleted);
|
|
||||||
FlushResult flushResult = flushAsync.GetAwaiter().GetResult();
|
|
||||||
Assert.True(flushResult.IsCanceled);
|
|
||||||
await writableBuffer.FlushAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class HangingStream : MemoryStream
|
|
||||||
{
|
|
||||||
public HangingStream()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await Task.Delay(30000, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task FlushAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await Task.Delay(30000, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await Task.Delay(30000, cancellationToken);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keeping as this code will eventually be ported to corefx
|
|
||||||
#if NETCOREAPP3_0
|
|
||||||
public override async ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await Task.Delay(30000, cancellationToken);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class SingleWriteStream : MemoryStream
|
|
||||||
{
|
|
||||||
private bool _shouldNextWriteFail;
|
|
||||||
|
|
||||||
public bool AllowAllWrites { get; set; }
|
|
||||||
|
|
||||||
// Keeping as this code will eventually be ported to corefx
|
|
||||||
#if NETCOREAPP3_0
|
|
||||||
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_shouldNextWriteFail && !AllowAllWrites)
|
|
||||||
{
|
|
||||||
await Task.Delay(30000, cancellationToken);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await base.WriteAsync(source, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_shouldNextWriteFail = !_shouldNextWriteFail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_shouldNextWriteFail && !AllowAllWrites)
|
|
||||||
{
|
|
||||||
await Task.Delay(30000, cancellationToken);
|
|
||||||
}
|
|
||||||
await base.WriteAsync(buffer, offset, count, cancellationToken);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_shouldNextWriteFail = !_shouldNextWriteFail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CannotFlushStream : MemoryStream
|
|
||||||
{
|
|
||||||
public override async Task FlushAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await Task.Delay(30000, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class TestWriterExtensions
|
|
||||||
{
|
|
||||||
public static PipeWriter WriteEmpty(this PipeWriter Writer, int count)
|
|
||||||
{
|
|
||||||
Writer.GetSpan(count).Slice(0, count).Fill(0);
|
|
||||||
Writer.Advance(count);
|
|
||||||
return Writer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class TestMemoryPool : MemoryPool<byte>
|
|
||||||
{
|
|
||||||
private MemoryPool<byte> _pool;
|
|
||||||
private int _maxBufferSize;
|
|
||||||
private bool _disposed;
|
|
||||||
private int _rentCount;
|
|
||||||
|
|
||||||
public TestMemoryPool(int maxBufferSize = 4096)
|
|
||||||
{
|
|
||||||
_pool = new CustomMemoryPool<byte>();
|
|
||||||
_maxBufferSize = maxBufferSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int MaxBufferSize => _maxBufferSize;
|
|
||||||
|
|
||||||
internal void CheckDisposed()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
throw new ObjectDisposedException(nameof(TestMemoryPool));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PooledMemory : MemoryManager<byte>
|
|
||||||
{
|
|
||||||
private IMemoryOwner<byte> _owner;
|
|
||||||
|
|
||||||
private readonly TestMemoryPool _pool;
|
|
||||||
|
|
||||||
private int _referenceCount;
|
|
||||||
|
|
||||||
private bool _returned;
|
|
||||||
|
|
||||||
private string _leaser;
|
|
||||||
|
|
||||||
public PooledMemory(IMemoryOwner<byte> owner, TestMemoryPool pool)
|
|
||||||
{
|
|
||||||
_owner = owner;
|
|
||||||
_pool = pool;
|
|
||||||
_leaser = Environment.StackTrace;
|
|
||||||
_referenceCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
~PooledMemory()
|
|
||||||
{
|
|
||||||
Debug.Assert(_returned, "Block being garbage collected instead of returned to pool" + Environment.NewLine + _leaser);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
_pool._rentCount--;
|
|
||||||
_pool.CheckDisposed();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override MemoryHandle Pin(int elementIndex = 0)
|
|
||||||
{
|
|
||||||
_pool.CheckDisposed();
|
|
||||||
Interlocked.Increment(ref _referenceCount);
|
|
||||||
|
|
||||||
if (!MemoryMarshal.TryGetArray(_owner.Memory, out ArraySegment<byte> segment))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if ((uint)elementIndex > (uint)segment.Count)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(elementIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
GCHandle handle = GCHandle.Alloc(segment.Array, GCHandleType.Pinned);
|
|
||||||
|
|
||||||
return new MemoryHandle(Unsafe.Add<byte>(((void*)handle.AddrOfPinnedObject()), elementIndex + segment.Offset), handle, this);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Unpin();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Unpin()
|
|
||||||
{
|
|
||||||
_pool.CheckDisposed();
|
|
||||||
|
|
||||||
int newRefCount = Interlocked.Decrement(ref _referenceCount);
|
|
||||||
|
|
||||||
if (newRefCount < 0)
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
|
|
||||||
if (newRefCount == 0)
|
|
||||||
{
|
|
||||||
_returned = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool TryGetArray(out ArraySegment<byte> segment)
|
|
||||||
{
|
|
||||||
_pool.CheckDisposed();
|
|
||||||
return MemoryMarshal.TryGetArray(_owner.Memory, out segment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Memory<byte> Memory
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
_pool.CheckDisposed();
|
|
||||||
return _owner.Memory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Span<byte> GetSpan()
|
|
||||||
{
|
|
||||||
_pool.CheckDisposed();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
// 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.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class WriteOnlyPipeStreamTests : PipeStreamTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void CanSeekFalse()
|
|
||||||
{
|
|
||||||
Assert.False(WritingStream.CanSeek);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanReadFalse()
|
|
||||||
{
|
|
||||||
Assert.False(WritingStream.CanRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanWriteTrue()
|
|
||||||
{
|
|
||||||
Assert.True(WritingStream.CanWrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void LengthThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => WritingStream.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void PositionThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => WritingStream.Position);
|
|
||||||
Assert.Throws<NotSupportedException>(() => WritingStream.Position = 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SeekThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => WritingStream.Seek(0, SeekOrigin.Begin));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SetLengthThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => WritingStream.SetLength(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ReadThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => WritingStream.Read(new byte[1], 0, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadAsyncThrows()
|
|
||||||
{
|
|
||||||
await Assert.ThrowsAsync<NotSupportedException>(async () => await WritingStream.ReadAsync(new byte[1], 0, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ReadTimeoutThrows()
|
|
||||||
{
|
|
||||||
Assert.Throws<NotSupportedException>(() => WritingStream.ReadTimeout = 1);
|
|
||||||
Assert.Throws<NotSupportedException>(() => WritingStream.ReadTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteAsyncWithReadOnlyMemoryWorks()
|
|
||||||
{
|
|
||||||
var expected = "Hello World!";
|
|
||||||
|
|
||||||
await WriteStringToStreamAsync(expected);
|
|
||||||
|
|
||||||
Assert.Equal(expected, await ReadFromPipeAsStringAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteAsyncWithArrayWorks()
|
|
||||||
{
|
|
||||||
var expected = new byte[1];
|
|
||||||
|
|
||||||
await WritingStream.WriteAsync(expected, 0, expected.Length);
|
|
||||||
|
|
||||||
Assert.Equal(expected, await ReadFromPipeAsByteArrayAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task BasicLargeWrite()
|
|
||||||
{
|
|
||||||
var expected = new byte[8000];
|
|
||||||
|
|
||||||
await WritingStream.WriteAsync(expected);
|
|
||||||
|
|
||||||
Assert.Equal(expected, await ReadFromPipeAsByteArrayAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FlushAsyncIsCalledFromCallingFlush()
|
|
||||||
{
|
|
||||||
var pipeWriter = new Mock<PipeWriter>();
|
|
||||||
var stream = new WriteOnlyPipeStream(pipeWriter.Object);
|
|
||||||
|
|
||||||
stream.Flush();
|
|
||||||
|
|
||||||
pipeWriter.Verify(m => m.FlushAsync(default));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task FlushAsyncIsCalledFromCallingFlushAsync()
|
|
||||||
{
|
|
||||||
var pipeWriter = new Mock<PipeWriter>();
|
|
||||||
var stream = new WriteOnlyPipeStream(pipeWriter.Object);
|
|
||||||
|
|
||||||
await stream.FlushAsync();
|
|
||||||
|
|
||||||
pipeWriter.Verify(m => m.FlushAsync(default));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task FlushAsyncCancellationTokenIsPassedIntoFlushAsync()
|
|
||||||
{
|
|
||||||
var pipeWriter = new Mock<PipeWriter>();
|
|
||||||
var stream = new WriteOnlyPipeStream(pipeWriter.Object);
|
|
||||||
var token = new CancellationToken();
|
|
||||||
|
|
||||||
await stream.FlushAsync(token);
|
|
||||||
|
|
||||||
pipeWriter.Verify(m => m.FlushAsync(token));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void WriteAsyncIsCalledFromCallingWrite()
|
|
||||||
{
|
|
||||||
var pipeWriter = new Mock<PipeWriter>();
|
|
||||||
var stream = new WriteOnlyPipeStream(pipeWriter.Object);
|
|
||||||
|
|
||||||
stream.Write(new byte[1]);
|
|
||||||
|
|
||||||
pipeWriter.Verify(m => m.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteAsyncIsCalledFromCallingWriteAsync()
|
|
||||||
{
|
|
||||||
var pipeWriter = new Mock<PipeWriter>();
|
|
||||||
var stream = new WriteOnlyPipeStream(pipeWriter.Object);
|
|
||||||
|
|
||||||
await stream.WriteAsync(new byte[1]);
|
|
||||||
|
|
||||||
pipeWriter.Verify(m => m.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteAsyncCancellationTokenIsPassedIntoWriteAsync()
|
|
||||||
{
|
|
||||||
var pipeWriter = new Mock<PipeWriter>();
|
|
||||||
var stream = new WriteOnlyPipeStream(pipeWriter.Object);
|
|
||||||
var token = new CancellationToken();
|
|
||||||
|
|
||||||
await stream.WriteAsync(new byte[1], token);
|
|
||||||
|
|
||||||
pipeWriter.Verify(m => m.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), token));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void WriteAsyncIsCalledFromBeginWrite()
|
|
||||||
{
|
|
||||||
var pipeWriter = new Mock<PipeWriter>();
|
|
||||||
var stream = new WriteOnlyPipeStream(pipeWriter.Object);
|
|
||||||
stream.BeginWrite(new byte[1], 0, 1, null, this);
|
|
||||||
pipeWriter.Verify(m => m.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), It.IsAny<CancellationToken>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task BeginAndEndWriteWork()
|
|
||||||
{
|
|
||||||
var expected = new byte[1];
|
|
||||||
var asyncResult = WritingStream.BeginWrite(expected, 0, 1, null, this);
|
|
||||||
WritingStream.EndWrite(asyncResult);
|
|
||||||
Assert.Equal(expected, await ReadFromPipeAsByteArrayAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BlockSyncIOThrows()
|
|
||||||
{
|
|
||||||
var writeOnlyPipeStream = new WriteOnlyPipeStream(Writer, allowSynchronousIO: false);
|
|
||||||
Assert.Throws<InvalidOperationException>(() => writeOnlyPipeStream.Write(new byte[0], 0, 0));
|
|
||||||
Assert.Throws<InvalidOperationException>(() => writeOnlyPipeStream.Flush());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void InnerPipeWriterReturnsPipeWriter()
|
|
||||||
{
|
|
||||||
var writeOnlyPipeStream = new WriteOnlyPipeStream(Writer, allowSynchronousIO: false);
|
|
||||||
Assert.Equal(Writer, writeOnlyPipeStream.InnerPipeWriter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
// 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.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace System.IO.Pipelines.Tests
|
|
||||||
{
|
|
||||||
public class WritingAdaptersInteropTests : PipeStreamTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task CheckBasicWritePipeApi()
|
|
||||||
{
|
|
||||||
var pipe = new Pipe();
|
|
||||||
var writeOnlyStream = new WriteOnlyPipeStream(pipe.Writer);
|
|
||||||
var pipeWriter = new StreamPipeWriter(writeOnlyStream);
|
|
||||||
await pipeWriter.WriteAsync(new byte[10]);
|
|
||||||
|
|
||||||
var res = await pipe.Reader.ReadAsync();
|
|
||||||
Assert.Equal(new byte[10], res.Buffer.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CheckNestedPipeApi()
|
|
||||||
{
|
|
||||||
var pipe = new Pipe();
|
|
||||||
var writer = pipe.Writer;
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
var writeOnlyStream = new WriteOnlyPipeStream(writer);
|
|
||||||
writer = new StreamPipeWriter(writeOnlyStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
await writer.WriteAsync(new byte[10]);
|
|
||||||
|
|
||||||
var res = await pipe.Reader.ReadAsync();
|
|
||||||
Assert.Equal(new byte[10], res.Buffer.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CheckBasicWriteStreamApi()
|
|
||||||
{
|
|
||||||
var stream = new MemoryStream();
|
|
||||||
var pipeWriter = new StreamPipeWriter(stream);
|
|
||||||
var writeOnlyStream = new WriteOnlyPipeStream(pipeWriter);
|
|
||||||
|
|
||||||
await writeOnlyStream.WriteAsync(new byte[10]);
|
|
||||||
|
|
||||||
stream.Position = 0;
|
|
||||||
var res = await ReadFromStreamAsByteArrayAsync(10, stream);
|
|
||||||
Assert.Equal(new byte[10], res);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CheckNestedStreamApi()
|
|
||||||
{
|
|
||||||
var stream = new MemoryStream();
|
|
||||||
Stream writeOnlyStream = stream;
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
var pipeWriter = new StreamPipeWriter(writeOnlyStream);
|
|
||||||
writeOnlyStream = new WriteOnlyPipeStream(pipeWriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeOnlyStream.WriteAsync(new byte[10]);
|
|
||||||
|
|
||||||
stream.Position = 0;
|
|
||||||
var res = await ReadFromStreamAsByteArrayAsync(10, stream);
|
|
||||||
Assert.Equal(new byte[10], res);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WritesCanBeCanceledViaProvidedCancellationToken()
|
|
||||||
{
|
|
||||||
var writeOnlyStream = new WriteOnlyPipeStream(new HangingPipeWriter());
|
|
||||||
var pipeWriter = new StreamPipeWriter(writeOnlyStream);
|
|
||||||
var cts = new CancellationTokenSource(1);
|
|
||||||
await Assert.ThrowsAsync<TaskCanceledException>(async () => await pipeWriter.WriteAsync(new byte[1], cts.Token));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteCanBeCanceledViaCancelPendingFlushWhenFlushIsAsync()
|
|
||||||
{
|
|
||||||
var writeOnlyStream = new WriteOnlyPipeStream(new HangingPipeWriter());
|
|
||||||
var pipeWriter = new StreamPipeWriter(writeOnlyStream);
|
|
||||||
|
|
||||||
FlushResult flushResult = new FlushResult();
|
|
||||||
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
var task = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var writingTask = pipeWriter.WriteAsync(new byte[1]);
|
|
||||||
tcs.SetResult(0);
|
|
||||||
flushResult = await writingTask;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(ex.Message);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await tcs.Task;
|
|
||||||
|
|
||||||
pipeWriter.CancelPendingFlush();
|
|
||||||
|
|
||||||
await task;
|
|
||||||
|
|
||||||
Assert.True(flushResult.IsCanceled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class HangingPipeWriter : PipeWriter
|
|
||||||
{
|
|
||||||
public override void Advance(int bytes)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CancelPendingFlush()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Complete(Exception exception = null)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await Task.Delay(30000, cancellationToken);
|
|
||||||
return new FlushResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Memory<byte> GetMemory(int sizeHint = 0)
|
|
||||||
{
|
|
||||||
return new Memory<byte>(new byte[4096]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Span<byte> GetSpan(int sizeHint = 0)
|
|
||||||
{
|
|
||||||
return new Span<byte>(new byte[4096]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnReaderCompleted(Action<Exception, object> callback, object state)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -98,39 +98,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
Stream IHttpRequestFeature.Body
|
Stream IHttpRequestFeature.Body
|
||||||
{
|
{
|
||||||
get
|
get => RequestBody;
|
||||||
{
|
set => RequestBody = value;
|
||||||
return RequestBody;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
RequestBody = value;
|
|
||||||
var requestPipeReader = new StreamPipeReader(RequestBody, new StreamPipeReaderAdapterOptions(
|
|
||||||
minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize(),
|
|
||||||
minimumReadThreshold: _context.MemoryPool.GetMinimumAllocSize(),
|
|
||||||
_context.MemoryPool));
|
|
||||||
RequestBodyPipeReader = requestPipeReader;
|
|
||||||
|
|
||||||
// The StreamPipeWrapper needs to be disposed as it hold onto blocks of memory
|
|
||||||
if (_wrapperObjectsToDispose == null)
|
|
||||||
{
|
|
||||||
_wrapperObjectsToDispose = new List<IDisposable>();
|
|
||||||
}
|
|
||||||
_wrapperObjectsToDispose.Add(requestPipeReader);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PipeReader IRequestBodyPipeFeature.Reader
|
PipeReader IRequestBodyPipeFeature.Reader
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (!ReferenceEquals(_requestStreamInternal, RequestBody))
|
||||||
|
{
|
||||||
|
_requestStreamInternal = RequestBody;
|
||||||
|
RequestBodyPipeReader = PipeReader.Create(RequestBody);
|
||||||
|
|
||||||
|
OnCompleted((self) =>
|
||||||
|
{
|
||||||
|
((PipeWriter)self).Complete();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, RequestBodyPipeReader);
|
||||||
|
}
|
||||||
|
|
||||||
return RequestBodyPipeReader;
|
return RequestBodyPipeReader;
|
||||||
}
|
}
|
||||||
set
|
|
||||||
{
|
|
||||||
RequestBodyPipeReader = value;
|
|
||||||
RequestBody = new ReadOnlyPipeStream(RequestBodyPipeReader);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IHttpRequestTrailersFeature.Available => RequestTrailersAvailable;
|
bool IHttpRequestTrailersFeature.Available => RequestTrailersAvailable;
|
||||||
|
|
@ -241,37 +230,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stream IHttpResponseFeature.Body
|
||||||
|
{
|
||||||
|
get => ResponseBody;
|
||||||
|
set => ResponseBody = value;
|
||||||
|
}
|
||||||
|
|
||||||
PipeWriter IResponseBodyPipeFeature.Writer
|
PipeWriter IResponseBodyPipeFeature.Writer
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return ResponsePipeWriter;
|
if (!ReferenceEquals(_responseStreamInternal, ResponseBody))
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
ResponsePipeWriter = value;
|
|
||||||
ResponseBody = new WriteOnlyPipeStream(ResponsePipeWriter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream IHttpResponseFeature.Body
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return ResponseBody;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
ResponseBody = value;
|
|
||||||
var responsePipeWriter = new StreamPipeWriter(ResponseBody, minimumSegmentSize: _context.MemoryPool.GetMinimumSegmentSize(), _context.MemoryPool);
|
|
||||||
ResponsePipeWriter = responsePipeWriter;
|
|
||||||
|
|
||||||
// The StreamPipeWrapper needs to be disposed as it hold onto blocks of memory
|
|
||||||
if (_wrapperObjectsToDispose == null)
|
|
||||||
{
|
{
|
||||||
_wrapperObjectsToDispose = new List<IDisposable>();
|
_responseStreamInternal = ResponseBody;
|
||||||
|
ResponseBodyPipeWriter = PipeWriter.Create(ResponseBody);
|
||||||
|
|
||||||
|
OnCompleted((self) =>
|
||||||
|
{
|
||||||
|
((PipeReader)self).Complete();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, ResponseBodyPipeWriter);
|
||||||
}
|
}
|
||||||
_wrapperObjectsToDispose.Add(responsePipeWriter);
|
|
||||||
|
return ResponseBodyPipeWriter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
protected string _methodText = null;
|
protected string _methodText = null;
|
||||||
private string _scheme = null;
|
private string _scheme = null;
|
||||||
|
private Stream _requestStreamInternal;
|
||||||
private List<IDisposable> _wrapperObjectsToDispose;
|
private Stream _responseStreamInternal;
|
||||||
|
|
||||||
public HttpProtocol(HttpConnectionContext context)
|
public HttpProtocol(HttpConnectionContext context)
|
||||||
{
|
{
|
||||||
|
|
@ -245,7 +245,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
public IHeaderDictionary ResponseHeaders { get; set; }
|
public IHeaderDictionary ResponseHeaders { get; set; }
|
||||||
public Stream ResponseBody { get; set; }
|
public Stream ResponseBody { get; set; }
|
||||||
public PipeWriter ResponsePipeWriter { get; set; }
|
public PipeWriter ResponseBodyPipeWriter { get; set; }
|
||||||
|
|
||||||
public CancellationToken RequestAborted
|
public CancellationToken RequestAborted
|
||||||
{
|
{
|
||||||
|
|
@ -317,7 +317,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
bodyControl = new BodyControl(bodyControl: this, this);
|
bodyControl = new BodyControl(bodyControl: this, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
(RequestBody, ResponseBody, RequestBodyPipeReader, ResponsePipeWriter) = bodyControl.Start(messageBody);
|
(RequestBody, ResponseBody, RequestBodyPipeReader, ResponseBodyPipeWriter) = bodyControl.Start(messageBody);
|
||||||
|
_requestStreamInternal = RequestBody;
|
||||||
|
_responseStreamInternal = ResponseBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopBodies() => bodyControl.Stop();
|
public void StopBodies() => bodyControl.Stop();
|
||||||
|
|
@ -400,14 +402,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
|
|
||||||
localAbortCts?.Dispose();
|
localAbortCts?.Dispose();
|
||||||
|
|
||||||
if (_wrapperObjectsToDispose != null)
|
|
||||||
{
|
|
||||||
foreach (var disposable in _wrapperObjectsToDispose)
|
|
||||||
{
|
|
||||||
disposable.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Output?.Reset();
|
Output?.Reset();
|
||||||
|
|
||||||
_requestHeadersParsed = 0;
|
_requestHeadersParsed = 0;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -11,26 +12,45 @@ using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
internal sealed class HttpRequestStream : ReadOnlyPipeStream
|
internal sealed class HttpRequestStream : Stream
|
||||||
{
|
{
|
||||||
private HttpRequestPipeReader _pipeReader;
|
private HttpRequestPipeReader _pipeReader;
|
||||||
private readonly IHttpBodyControlFeature _bodyControl;
|
private readonly IHttpBodyControlFeature _bodyControl;
|
||||||
|
|
||||||
public HttpRequestStream(IHttpBodyControlFeature bodyControl, HttpRequestPipeReader pipeReader)
|
public HttpRequestStream(IHttpBodyControlFeature bodyControl, HttpRequestPipeReader pipeReader)
|
||||||
: base (pipeReader)
|
|
||||||
{
|
{
|
||||||
_bodyControl = bodyControl;
|
_bodyControl = bodyControl;
|
||||||
_pipeReader = pipeReader;
|
_pipeReader = pipeReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public override bool CanSeek => false;
|
||||||
|
|
||||||
|
public override bool CanRead => true;
|
||||||
|
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
|
||||||
|
public override long Length => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override long Position
|
||||||
{
|
{
|
||||||
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
|
get => throw new NotSupportedException();
|
||||||
|
set => throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int WriteTimeout
|
||||||
|
{
|
||||||
|
get => throw new NotSupportedException();
|
||||||
|
set => throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
|
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return ReadAsyncInternal(destination, cancellationToken);
|
return ReadAsyncWrapper(destination, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return ReadAsyncWrapper(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
|
@ -43,16 +63,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValueTask<int> ReadAsyncInternal(Memory<byte> buffer, CancellationToken cancellationToken)
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
=> throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
=> throw new NotSupportedException();
|
||||||
|
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
{
|
{
|
||||||
try
|
throw new NotSupportedException();
|
||||||
{
|
}
|
||||||
return base.ReadAsync(buffer, cancellationToken);
|
|
||||||
}
|
public override void SetLength(long value)
|
||||||
catch (ConnectionAbortedException ex)
|
{
|
||||||
{
|
throw new NotSupportedException();
|
||||||
throw new TaskCanceledException("The request was aborted", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Flush()
|
public override void Flush()
|
||||||
|
|
@ -63,5 +88,144 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||||
|
{
|
||||||
|
var task = ReadAsync(buffer, offset, count, default, state);
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
task.ContinueWith(t => callback.Invoke(t));
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int EndRead(IAsyncResult asyncResult)
|
||||||
|
{
|
||||||
|
return ((Task<int>)asyncResult).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<int>(state);
|
||||||
|
var task = ReadAsync(buffer, offset, count, cancellationToken);
|
||||||
|
task.ContinueWith((task2, state2) =>
|
||||||
|
{
|
||||||
|
var tcs2 = (TaskCompletionSource<int>)state2;
|
||||||
|
if (task2.IsCanceled)
|
||||||
|
{
|
||||||
|
tcs2.SetCanceled();
|
||||||
|
}
|
||||||
|
else if (task2.IsFaulted)
|
||||||
|
{
|
||||||
|
tcs2.SetException(task2.Exception);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tcs2.SetResult(task2.Result);
|
||||||
|
}
|
||||||
|
}, tcs, cancellationToken);
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueTask<int> ReadAsyncWrapper(Memory<byte> destination, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ReadAsyncInternal(destination, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (ConnectionAbortedException ex)
|
||||||
|
{
|
||||||
|
throw new TaskCanceledException("The request was aborted", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask<int> ReadAsyncInternal(Memory<byte> buffer, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var result = await _pipeReader.ReadAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsCanceled)
|
||||||
|
{
|
||||||
|
throw new OperationCanceledException("The read was canceled");
|
||||||
|
}
|
||||||
|
|
||||||
|
var readableBuffer = result.Buffer;
|
||||||
|
var readableBufferLength = readableBuffer.Length;
|
||||||
|
|
||||||
|
var consumed = readableBuffer.End;
|
||||||
|
var actual = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (readableBufferLength != 0)
|
||||||
|
{
|
||||||
|
actual = (int)Math.Min(readableBufferLength, buffer.Length);
|
||||||
|
|
||||||
|
var slice = actual == readableBufferLength ? readableBuffer : readableBuffer.Slice(0, actual);
|
||||||
|
consumed = slice.End;
|
||||||
|
slice.CopyTo(buffer.Span);
|
||||||
|
|
||||||
|
return actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.IsCompleted)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_pipeReader.AdvanceTo(consumed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (destination == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferSize <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CopyToAsyncInternal(destination, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CopyToAsyncInternal(Stream destination, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var result = await _pipeReader.ReadAsync(cancellationToken);
|
||||||
|
var readableBuffer = result.Buffer;
|
||||||
|
var readableBufferLength = readableBuffer.Length;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (readableBufferLength != 0)
|
||||||
|
{
|
||||||
|
foreach (var memory in readableBuffer)
|
||||||
|
{
|
||||||
|
await destination.WriteAsync(memory, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.IsCompleted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_pipeReader.AdvanceTo(readableBuffer.End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,33 +2,52 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
internal sealed class HttpResponseStream : WriteOnlyPipeStream
|
internal sealed class HttpResponseStream : Stream
|
||||||
{
|
{
|
||||||
private readonly HttpResponsePipeWriter _pipeWriter;
|
private readonly HttpResponsePipeWriter _pipeWriter;
|
||||||
private readonly IHttpBodyControlFeature _bodyControl;
|
private readonly IHttpBodyControlFeature _bodyControl;
|
||||||
|
|
||||||
public HttpResponseStream(IHttpBodyControlFeature bodyControl, HttpResponsePipeWriter pipeWriter)
|
public HttpResponseStream(IHttpBodyControlFeature bodyControl, HttpResponsePipeWriter pipeWriter)
|
||||||
: base(pipeWriter)
|
|
||||||
{
|
{
|
||||||
_bodyControl = bodyControl;
|
_bodyControl = bodyControl;
|
||||||
_pipeWriter = pipeWriter;
|
_pipeWriter = pipeWriter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
public override bool CanSeek => false;
|
||||||
{
|
|
||||||
if (!_bodyControl.AllowSynchronousIO)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Write(buffer, offset, count);
|
public override bool CanRead => false;
|
||||||
|
|
||||||
|
public override bool CanWrite => true;
|
||||||
|
|
||||||
|
public override long Length => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => throw new NotSupportedException();
|
||||||
|
set => throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int ReadTimeout
|
||||||
|
{
|
||||||
|
get => throw new NotSupportedException();
|
||||||
|
set => throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
=> throw new NotSupportedException();
|
||||||
|
|
||||||
|
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
=> throw new NotSupportedException();
|
||||||
|
|
||||||
public override void Flush()
|
public override void Flush()
|
||||||
{
|
{
|
||||||
if (!_bodyControl.AllowSynchronousIO)
|
if (!_bodyControl.AllowSynchronousIO)
|
||||||
|
|
@ -36,7 +55,87 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
|
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Flush();
|
FlushAsync(default).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _pipeWriter.FlushAsync(cancellationToken).GetAsTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (!_bodyControl.AllowSynchronousIO)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteAsync(buffer, offset, count, default).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||||
|
{
|
||||||
|
var task = WriteAsync(buffer, offset, count, default, state);
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
task.ContinueWith(t => callback.Invoke(t));
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void EndWrite(IAsyncResult asyncResult)
|
||||||
|
{
|
||||||
|
((Task<object>)asyncResult).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<object>(state);
|
||||||
|
var task = WriteAsync(buffer, offset, count, cancellationToken);
|
||||||
|
task.ContinueWith((task2, state2) =>
|
||||||
|
{
|
||||||
|
var tcs2 = (TaskCompletionSource<object>)state2;
|
||||||
|
if (task2.IsCanceled)
|
||||||
|
{
|
||||||
|
tcs2.SetCanceled();
|
||||||
|
}
|
||||||
|
else if (task2.IsFaulted)
|
||||||
|
{
|
||||||
|
tcs2.SetException(task2.Exception);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tcs2.SetResult(null);
|
||||||
|
}
|
||||||
|
}, tcs, cancellationToken);
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return WriteAsyncInternal(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return new ValueTask(WriteAsyncInternal(source, cancellationToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return _pipeWriter.WriteAsync(source, cancellationToken).GetAsTask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,30 +57,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task PipesAreNotPersistedBySettingStreamPipeWriterAcrossRequests()
|
|
||||||
{
|
|
||||||
var responseBodyPersisted = false;
|
|
||||||
PipeWriter bodyPipe = null;
|
|
||||||
await using (var server = new TestServer(async context =>
|
|
||||||
{
|
|
||||||
if (context.Response.BodyWriter == bodyPipe)
|
|
||||||
{
|
|
||||||
responseBodyPersisted = true;
|
|
||||||
}
|
|
||||||
bodyPipe = new StreamPipeWriter(new MemoryStream());
|
|
||||||
context.Response.BodyWriter = bodyPipe;
|
|
||||||
|
|
||||||
await context.Response.WriteAsync("hello, world");
|
|
||||||
}, new TestServiceContext(LoggerFactory)))
|
|
||||||
{
|
|
||||||
Assert.Equal(string.Empty, await server.HttpClientSlim.GetStringAsync($"http://localhost:{server.Port}/"));
|
|
||||||
Assert.Equal(string.Empty, await server.HttpClientSlim.GetStringAsync($"http://localhost:{server.Port}/"));
|
|
||||||
|
|
||||||
Assert.False(responseBodyPersisted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PipesAreNotPersistedAcrossRequests()
|
public async Task PipesAreNotPersistedAcrossRequests()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3851,45 +3851,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ResponseSetBodyAndPipeBodyIsWrapped()
|
|
||||||
{
|
|
||||||
await using (var server = new TestServer(async httpContext =>
|
|
||||||
{
|
|
||||||
httpContext.Response.Body = new MemoryStream();
|
|
||||||
httpContext.Response.BodyWriter = new Pipe().Writer;
|
|
||||||
Assert.IsType<WriteOnlyPipeStream>(httpContext.Response.Body);
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}, new TestServiceContext(LoggerFactory)))
|
|
||||||
{
|
|
||||||
using (var connection = server.CreateConnection())
|
|
||||||
{
|
|
||||||
await connection.Send(
|
|
||||||
"GET / HTTP/1.1",
|
|
||||||
"Host:",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
await connection.Receive(
|
|
||||||
"HTTP/1.1 200 OK",
|
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
|
||||||
"Content-Length: 0",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResponseSetBodyToSameValueTwiceGetPipeMultipleTimesDifferentObject()
|
public async Task ResponseSetBodyToSameValueTwiceGetPipeMultipleTimesDifferentObject()
|
||||||
{
|
{
|
||||||
await using (var server = new TestServer(async httpContext =>
|
await using (var server = new TestServer(async httpContext =>
|
||||||
{
|
{
|
||||||
var memoryStream = new MemoryStream();
|
httpContext.Response.Body = new MemoryStream();
|
||||||
httpContext.Response.Body = memoryStream;
|
|
||||||
var BodyWriter1 = httpContext.Response.BodyWriter;
|
var BodyWriter1 = httpContext.Response.BodyWriter;
|
||||||
|
|
||||||
httpContext.Response.Body = memoryStream;
|
httpContext.Response.Body = new MemoryStream();
|
||||||
var BodyWriter2 = httpContext.Response.BodyWriter;
|
var BodyWriter2 = httpContext.Response.BodyWriter;
|
||||||
|
|
||||||
Assert.NotEqual(BodyWriter1, BodyWriter2);
|
Assert.NotEqual(BodyWriter1, BodyWriter2);
|
||||||
|
|
@ -3913,104 +3883,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ResponseSetPipeToSameValueTwiceGetBodyMultipleTimesDifferent()
|
|
||||||
{
|
|
||||||
await using (var server = new TestServer(async httpContext =>
|
|
||||||
{
|
|
||||||
var pipeWriter = new Pipe().Writer;
|
|
||||||
httpContext.Response.BodyWriter = pipeWriter;
|
|
||||||
var body1 = httpContext.Response.Body;
|
|
||||||
|
|
||||||
httpContext.Response.BodyWriter = pipeWriter;
|
|
||||||
var body2 = httpContext.Response.Body;
|
|
||||||
|
|
||||||
Assert.NotEqual(body1, body2);
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}, new TestServiceContext(LoggerFactory)))
|
|
||||||
{
|
|
||||||
using (var connection = server.CreateConnection())
|
|
||||||
{
|
|
||||||
await connection.Send(
|
|
||||||
"GET / HTTP/1.1",
|
|
||||||
"Host:",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
await connection.Receive(
|
|
||||||
"HTTP/1.1 200 OK",
|
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
|
||||||
"Content-Length: 0",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ResponseSetPipeAndBodyWriterIsWrapped()
|
|
||||||
{
|
|
||||||
await using (var server = new TestServer(async httpContext =>
|
|
||||||
{
|
|
||||||
httpContext.Response.BodyWriter = new Pipe().Writer;
|
|
||||||
httpContext.Response.Body = new MemoryStream();
|
|
||||||
Assert.IsType<StreamPipeWriter>(httpContext.Response.BodyWriter);
|
|
||||||
Assert.IsType<MemoryStream>(httpContext.Response.Body);
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}, new TestServiceContext(LoggerFactory)))
|
|
||||||
{
|
|
||||||
using (var connection = server.CreateConnection())
|
|
||||||
{
|
|
||||||
await connection.Send(
|
|
||||||
"GET / HTTP/1.1",
|
|
||||||
"Host:",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
await connection.Receive(
|
|
||||||
"HTTP/1.1 200 OK",
|
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
|
||||||
"Content-Length: 0",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ResponseWriteToBodyWriterAndStreamAllBlocksDisposed()
|
|
||||||
{
|
|
||||||
await using (var server = new TestServer(async httpContext =>
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
httpContext.Response.BodyWriter = new Pipe().Writer;
|
|
||||||
await httpContext.Response.Body.WriteAsync(new byte[1]);
|
|
||||||
httpContext.Response.Body = new MemoryStream();
|
|
||||||
await httpContext.Response.BodyWriter.WriteAsync(new byte[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestMemoryPool will confirm that all rented blocks have been disposed, meaning dispose was called.
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}, new TestServiceContext(LoggerFactory)))
|
|
||||||
{
|
|
||||||
using (var connection = server.CreateConnection())
|
|
||||||
{
|
|
||||||
await connection.Send(
|
|
||||||
"GET / HTTP/1.1",
|
|
||||||
"Host:",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
await connection.Receive(
|
|
||||||
"HTTP/1.1 200 OK",
|
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
|
||||||
"Content-Length: 0",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResponseStreamWrappingWorks()
|
public async Task ResponseStreamWrappingWorks()
|
||||||
{
|
{
|
||||||
|
|
@ -4026,10 +3898,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
|
|
||||||
httpContext.Response.Body = oldBody;
|
httpContext.Response.Body = oldBody;
|
||||||
|
|
||||||
// Even though we are restoring the original response body, we will create a
|
|
||||||
// wrapper rather than restoring the original pipe.
|
|
||||||
Assert.IsType<StreamPipeWriter>(httpContext.Response.BodyWriter);
|
|
||||||
|
|
||||||
}, new TestServiceContext(LoggerFactory)))
|
}, new TestServiceContext(LoggerFactory)))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -4040,50 +3908,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
"HTTP/1.1 200 OK",
|
"HTTP/1.1 200 OK",
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
$"Date: {server.Context.DateHeaderValue}",
|
||||||
"Content-Length: 0",
|
"Content-Length: 0",
|
||||||
"",
|
"",
|
||||||
"");
|
"");
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ResponsePipeWrappingWorks()
|
|
||||||
{
|
|
||||||
await using (var server = new TestServer(async httpContext =>
|
|
||||||
{
|
|
||||||
var oldPipeWriter = httpContext.Response.BodyWriter;
|
|
||||||
var pipe = new Pipe();
|
|
||||||
httpContext.Response.BodyWriter = pipe.Writer;
|
|
||||||
|
|
||||||
await httpContext.Response.Body.WriteAsync(new byte[1]);
|
|
||||||
await httpContext.Response.BodyWriter.WriteAsync(new byte[1]);
|
|
||||||
|
|
||||||
var readResult = await pipe.Reader.ReadAsync();
|
|
||||||
Assert.Equal(2, readResult.Buffer.Length);
|
|
||||||
|
|
||||||
httpContext.Response.BodyWriter = oldPipeWriter;
|
|
||||||
|
|
||||||
// Even though we are restoring the original response body, we will create a
|
|
||||||
// wrapper rather than restoring the original pipe.
|
|
||||||
Assert.IsType<WriteOnlyPipeStream>(httpContext.Response.Body);
|
|
||||||
}, new TestServiceContext(LoggerFactory)))
|
|
||||||
{
|
|
||||||
using (var connection = server.CreateConnection())
|
|
||||||
{
|
|
||||||
await connection.Send(
|
|
||||||
"GET / HTTP/1.1",
|
|
||||||
"Host:",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
await connection.Receive(
|
|
||||||
"HTTP/1.1 200 OK",
|
|
||||||
$"Date: {server.Context.DateHeaderValue}",
|
|
||||||
"Content-Length: 0",
|
|
||||||
"",
|
|
||||||
"");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue