From 5573f26fc34ba040b03f022a5892a5e172573002 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 7 Feb 2018 19:29:45 -0800 Subject: [PATCH 01/14] Add System.IO.Pipelines --- .../packageIndex.json | 6 + pkg/descriptions.json | 9 + .../System.Threading.Channels.sln | 60 ++ src/System.IO.Pipelines/dir.props | 8 + .../pkg/System.IO.Pipelines.pkgproj | 10 + .../ref/Configurations.props | 9 + .../ref/System.IO.Pipelines.cs | 0 .../ref/System.IO.Pipelines.csproj | 20 + .../src/Configurations.props | 8 + .../src/System.IO.Pipelines.csproj | 47 + .../src/System/IO/Pipelines/BufferSegment.cs | 114 +++ .../src/System/IO/Pipelines/FlushResult.cs | 42 + .../src/System/IO/Pipelines/IPipeAwaiter.cs | 28 + .../System/IO/Pipelines/IPipeConnection.cs | 22 + .../System/IO/Pipelines/InlineScheduler.cs | 19 + .../src/System/IO/Pipelines/Pipe.cs | 859 ++++++++++++++++++ .../src/System/IO/Pipelines/PipeAwaitable.cs | 150 +++ .../src/System/IO/Pipelines/PipeAwaiter.cs | 41 + .../src/System/IO/Pipelines/PipeCompletion.cs | 117 +++ .../IO/Pipelines/PipeCompletionCallback.cs | 12 + .../IO/Pipelines/PipeCompletionCallbacks.cs | 64 ++ .../src/System/IO/Pipelines/PipeOptions.cs | 68 ++ .../src/System/IO/Pipelines/PipeReader.cs | 65 ++ .../System/IO/Pipelines/PipeReaderState.cs | 61 ++ .../src/System/IO/Pipelines/PipeScheduler.cs | 35 + .../src/System/IO/Pipelines/PipeWriter.cs | 58 ++ .../src/System/IO/Pipelines/ReadResult.cs | 49 + .../src/System/IO/Pipelines/ResultFlags.cs | 13 + .../IO/Pipelines/SpanLiteralExtensions.cs | 72 ++ .../IO/Pipelines/ThreadPoolScheduler.cs | 56 ++ .../src/System/IO/Pipelines/ThrowHelper.cs | 154 ++++ .../tests/Configurations.props | 8 + .../tests/System.IO.Pipelines.Tests.csproj | 12 + src/System.Memory/src/System.Memory.csproj | 1 + .../src/System/Buffers/IBufferWriter.cs | 28 + 35 files changed, 2325 insertions(+) create mode 100644 src/System.IO.Pipelines/System.Threading.Channels.sln create mode 100644 src/System.IO.Pipelines/dir.props create mode 100644 src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj create mode 100644 src/System.IO.Pipelines/ref/Configurations.props create mode 100644 src/System.IO.Pipelines/ref/System.IO.Pipelines.cs create mode 100644 src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj create mode 100644 src/System.IO.Pipelines/src/Configurations.props create mode 100644 src/System.IO.Pipelines/src/System.IO.Pipelines.csproj create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/FlushResult.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeAwaiter.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeConnection.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/InlineScheduler.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaiter.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallback.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallbacks.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReader.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/ResultFlags.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/SpanLiteralExtensions.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs create mode 100644 src/System.IO.Pipelines/tests/Configurations.props create mode 100644 src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj create mode 100644 src/System.Memory/src/System/Buffers/IBufferWriter.cs diff --git a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json index 28e475f5bd0c..946567d1b94e 100644 --- a/pkg/Microsoft.Private.PackageBaseline/packageIndex.json +++ b/pkg/Microsoft.Private.PackageBaseline/packageIndex.json @@ -2120,6 +2120,12 @@ "4.0.3.0": "4.5.0" } }, + "System.IO.Pipelines": { + "InboxOn": {}, + "AssemblyVersionInPackageVersion": { + "4.0.0.0": "4.5.0" + } + }, "System.IO.Pipes": { "StableVersions": [ "4.0.0", diff --git a/pkg/descriptions.json b/pkg/descriptions.json index 83319dc0b735..3345d8958ec2 100644 --- a/pkg/descriptions.json +++ b/pkg/descriptions.json @@ -892,6 +892,15 @@ "Description": "Provides classes that support storage of multiple data objects in a single container.", "CommonTypes": [] }, + { + "Name": "System.IO.Pipelines", + "Description": "Single producer single consumer byte buffer management.", + "CommonTypes": [ + "System.IO.Pipelines.Pipe", + "System.IO.Pipelines.PipeWriter", + "System.IO.Pipelines.PipeReader" + ] + }, { "Name": "System.IO.Pipes", "Description": "Provides a means for interprocess communication through anonymous and/or named pipes.", diff --git a/src/System.IO.Pipelines/System.Threading.Channels.sln b/src/System.IO.Pipelines/System.Threading.Channels.sln new file mode 100644 index 000000000000..f85347c9a1fb --- /dev/null +++ b/src/System.IO.Pipelines/System.Threading.Channels.sln @@ -0,0 +1,60 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Pipelines.Tests", "tests\System.IO.Pipelines.Tests.csproj", "{9E984EB2-827E-4029-9647-FB5F8B67C553}" + ProjectSection(ProjectDependencies) = postProject + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} = {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Pipelines.Performance.Tests", "tests\Performance\System.IO.Pipelines.Performance.Tests.csproj", "{11ABE2F8-4FB9-48AC-91AA-D04503059550}" + ProjectSection(ProjectDependencies) = postProject + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} = {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Pipelines", "src\System.IO.Pipelines.csproj", "{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}" + ProjectSection(ProjectDependencies) = postProject + {9C524CA0-92FF-437B-B568-BCE8A794A69A} = {9C524CA0-92FF-437B-B568-BCE8A794A69A} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Pipelines", "ref\System.IO.Pipelines.csproj", "{9C524CA0-92FF-437B-B568-BCE8A794A69A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1A2F9F4A-A032-433E-B914-ADD5992BB178}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E107E9C1-E893-4E87-987E-04EF0DCEAEFD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{2E666815-2EDB-464B-9DF6-380BF4789AD4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E984EB2-827E-4029-9647-FB5F8B67C553}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU + {9E984EB2-827E-4029-9647-FB5F8B67C553}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU + {9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU + {9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU + {11ABE2F8-4FB9-48AC-91AA-D04503059550}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU + {11ABE2F8-4FB9-48AC-91AA-D04503059550}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU + {11ABE2F8-4FB9-48AC-91AA-D04503059550}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU + {11ABE2F8-4FB9-48AC-91AA-D04503059550}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU + {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU + {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU + {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU + {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {9E984EB2-827E-4029-9647-FB5F8B67C553} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} + {11ABE2F8-4FB9-48AC-91AA-D04503059550} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD} + {9C524CA0-92FF-437B-B568-BCE8A794A69A} = {2E666815-2EDB-464B-9DF6-380BF4789AD4} + EndGlobalSection +EndGlobal diff --git a/src/System.IO.Pipelines/dir.props b/src/System.IO.Pipelines/dir.props new file mode 100644 index 000000000000..4356decc45ef --- /dev/null +++ b/src/System.IO.Pipelines/dir.props @@ -0,0 +1,8 @@ + + + + + 4.0.0.0 + Open + + \ No newline at end of file diff --git a/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj b/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj new file mode 100644 index 000000000000..75ac9fc474f9 --- /dev/null +++ b/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj @@ -0,0 +1,10 @@ + + + + + + net46;netcore50;netcoreapp1.0;$(UAPvNextTFM);$(AllXamarinFrameworks) + + + + diff --git a/src/System.IO.Pipelines/ref/Configurations.props b/src/System.IO.Pipelines/ref/Configurations.props new file mode 100644 index 000000000000..5f3b2623edf6 --- /dev/null +++ b/src/System.IO.Pipelines/ref/Configurations.props @@ -0,0 +1,9 @@ + + + + + netstandard1.3; + netstandard; + + + diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs b/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj new file mode 100644 index 000000000000..4be0d4695d9d --- /dev/null +++ b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj @@ -0,0 +1,20 @@ + + + + + {9C524CA0-92FF-437B-B568-BCE8A794A69A} + + + + + + + + + + + + + + + diff --git a/src/System.IO.Pipelines/src/Configurations.props b/src/System.IO.Pipelines/src/Configurations.props new file mode 100644 index 000000000000..78953dfc8851 --- /dev/null +++ b/src/System.IO.Pipelines/src/Configurations.props @@ -0,0 +1,8 @@ + + + + + netstandard; + + + diff --git a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj new file mode 100644 index 000000000000..fe2dd574bc6c --- /dev/null +++ b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj @@ -0,0 +1,47 @@ + + + + + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} + System.Threading.Channels + $(OutputPath)$(MSBuildProjectName).xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs new file mode 100644 index 000000000000..1ab947e28f99 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs @@ -0,0 +1,114 @@ +// 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.Buffers; +using System.Diagnostics; + +namespace System.IO.Pipelines +{ + internal class BufferSegment : IMemoryList + { + private OwnedMemory _ownedMemory; + + private int _end; + + /// + /// The Start represents the offset into AvailableMemory where the range of "active" bytes begins. At the point when the block is leased + /// the Start is guaranteed to be equal to 0. The value of Start may be assigned anywhere between 0 and + /// AvailableMemory.Length, and must be equal to or less than End. + /// + public int Start { get; private set; } + + /// + /// The End represents the offset into AvailableMemory where the range of "active" bytes ends. At the point when the block is leased + /// the End is guaranteed to be equal to Start. The value of Start may be assigned anywhere between 0 and + /// Buffer.Length, and must be equal to or less than End. + /// + public int End + { + get => _end; + set + { + Debug.Assert(Start - value <= AvailableMemory.Length); + + _end = value; + Memory = AvailableMemory.Slice(Start, _end - Start); + } + } + + /// + /// Reference to the next block of data when the overall "active" bytes spans multiple blocks. At the point when the block is + /// leased Next is guaranteed to be null. Start, End, and Next are used together in order to create a linked-list of discontiguous + /// working memory. The "active" memory is grown when bytes are copied in, End is increased, and Next is assigned. The "active" + /// memory is shrunk when bytes are consumed, Start is increased, and blocks are returned to the pool. + /// + public BufferSegment NextSegment { get; set; } + + /// + /// Combined length of all segments before this + /// + public long RunningIndex { get; private set; } + + public void SetMemory(OwnedMemory buffer) + { + SetMemory(buffer, 0, 0); + } + + public void SetMemory(OwnedMemory ownedMemory, int start, int end, bool readOnly = false) + { + _ownedMemory = ownedMemory; + _ownedMemory.Retain(); + + AvailableMemory = _ownedMemory.Memory; + + ReadOnly = readOnly; + RunningIndex = 0; + Start = start; + End = end; + NextSegment = null; + } + + public void ResetMemory() + { + _ownedMemory.Release(); + _ownedMemory = null; + AvailableMemory = default; + } + + public Memory AvailableMemory { get; private set; } + + public Memory Memory { get; private set; } + + public int Length => End - Start; + + public IMemoryList Next => NextSegment; + + /// + /// If true, data should not be written into the backing block after the End offset. Data between start and end should never be modified + /// since this would break cloning. + /// + public bool ReadOnly { get; private set; } + + /// + /// The amount of writable bytes in this segment. It is the amount of bytes between Length and End + /// + public int WritableBytes => AvailableMemory.Length - End; + + public void SetNext(BufferSegment segment) + { + Debug.Assert(segment != null); + Debug.Assert(Next == null); + + NextSegment = segment; + + segment = this; + + while (segment.Next != null) + { + segment.NextSegment.RunningIndex = segment.RunningIndex + segment.Length; + segment = segment.NextSegment; + } + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/FlushResult.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/FlushResult.cs new file mode 100644 index 000000000000..f40254684ab6 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/FlushResult.cs @@ -0,0 +1,42 @@ +// 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. + +namespace System.IO.Pipelines +{ + /// + /// Result returned by call + /// + public struct FlushResult + { + internal ResultFlags ResultFlags; + + /// + /// Creates a new instance of setting and flags + /// + public FlushResult(bool isCanceled, bool isCompleted) + { + ResultFlags = ResultFlags.None; + + if (isCanceled) + { + ResultFlags |= ResultFlags.Canceled; + } + + if (isCompleted) + { + ResultFlags |= ResultFlags.Completed; + } + } + + /// + /// True if the current operation was canceled, otherwise false. + /// + public bool IsCanceled => (ResultFlags & ResultFlags.Canceled) != 0; + + /// + /// True if the is complete otherwise false + /// + public bool IsCompleted => (ResultFlags & ResultFlags.Completed) != 0; + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeAwaiter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeAwaiter.cs new file mode 100644 index 000000000000..a460bbf896e3 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeAwaiter.cs @@ -0,0 +1,28 @@ +// 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. + +namespace System.IO.Pipelines +{ + /// + /// Represents a source of async operation + /// + public interface IPipeAwaiter + { + /// + /// Gets whether async operation has completed. + /// + bool IsCompleted { get; } + + /// + /// Gets the result async operation. + /// + /// + T GetResult(); + + /// + /// Schedules the continuation action that's invoked when the instance completes. + /// + void OnCompleted(Action continuation); + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeConnection.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeConnection.cs new file mode 100644 index 000000000000..f2f147b5726c --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeConnection.cs @@ -0,0 +1,22 @@ +// 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. + +namespace System.IO.Pipelines +{ + /// + /// Defines a class that provides a duplex pipe from which data can be read from and written to. + /// + public interface IDuplexPipe : IDisposable + { + /// + /// Gets the half of the duplex pipe. + /// + PipeReader Input { get; } + + /// + /// Gets the half of the duplex pipe. + /// + PipeWriter Output { get; } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/InlineScheduler.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/InlineScheduler.cs new file mode 100644 index 000000000000..2e34ec94c6c0 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/InlineScheduler.cs @@ -0,0 +1,19 @@ +// 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. + +namespace System.IO.Pipelines +{ + internal sealed class InlineScheduler : PipeScheduler + { + public override void Schedule(Action action) + { + action(); + } + + public override void Schedule(Action action, object state) + { + action(state); + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs new file mode 100644 index 000000000000..622e76b5d57b --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs @@ -0,0 +1,859 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.IO.Pipelines +{ + /// + /// Default and implementation. + /// + public sealed class Pipe : IPipeAwaiter, IPipeAwaiter + { + private const int SegmentPoolSize = 16; + + private static readonly Action _signalReaderAwaitable = state => ((Pipe)state).ReaderCancellationRequested(); + private static readonly Action _signalWriterAwaitable = state => ((Pipe)state).WriterCancellationRequested(); + private static readonly Action _invokeCompletionCallbacks = state => ((PipeCompletionCallbacks)state).Execute(); + + // This sync objects protects the following state: + // 1. _commitHead & _commitHeadIndex + // 2. _length + // 3. _readerAwaitable & _writerAwaitable + private readonly object _sync = new object(); + + private readonly MemoryPool _pool; + private readonly int _minimumSegmentSize; + private readonly long _maximumSizeHigh; + private readonly long _maximumSizeLow; + + private readonly PipeScheduler _readerScheduler; + private readonly PipeScheduler _writerScheduler; + + private long _length; + private long _currentWriteLength; + + private int _pooledSegmentCount; + + private PipeAwaitable _readerAwaitable; + private PipeAwaitable _writerAwaitable; + + private PipeCompletion _writerCompletion; + private PipeCompletion _readerCompletion; + + private readonly BufferSegment[] _bufferSegmentPool; + + // The read head which is the extent of the IPipelineReader's consumed bytes + private BufferSegment _readHead; + private int _readHeadIndex; + + // The commit head which is the extent of the bytes available to the IPipelineReader to consume + private BufferSegment _commitHead; + private int _commitHeadIndex; + + // The write head which is the extent of the IPipelineWriter's written bytes + private BufferSegment _writingHead; + + private PipeReaderState _readingState; + + private bool _disposed; + + internal long Length => _length; + + /// + /// Initializes the using as options. + /// + public Pipe() : this(PipeOptions.Default) + { + } + + /// + /// Initializes the with the specified . + /// + public Pipe(PipeOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (options.ResumeWriterThreshold < 0) + { + throw new ArgumentOutOfRangeException(nameof(options.ResumeWriterThreshold)); + } + + if (options.PauseWriterThreshold < 0) + { + throw new ArgumentOutOfRangeException(nameof(options.PauseWriterThreshold)); + } + + if (options.ResumeWriterThreshold > options.PauseWriterThreshold) + { + throw new ArgumentException(nameof(options.PauseWriterThreshold) + " should be greater or equal to " + nameof(options.ResumeWriterThreshold), nameof(options.PauseWriterThreshold)); + } + + _bufferSegmentPool = new BufferSegment[SegmentPoolSize]; + + _pool = options.Pool; + _minimumSegmentSize = options.MinimumSegmentSize; + _maximumSizeHigh = options.PauseWriterThreshold; + _maximumSizeLow = options.ResumeWriterThreshold; + _readerScheduler = options.ReaderScheduler ?? PipeScheduler.Inline; + _writerScheduler = options.WriterScheduler ?? PipeScheduler.Inline; + _readerAwaitable = new PipeAwaitable(completed: false); + _writerAwaitable = new PipeAwaitable(completed: true); + Reader = new DefaultPipeReader(this); + Writer = new DefaultPipeWriter(this); + } + + private void ResetState() + { + _readerCompletion.Reset(); + _writerCompletion.Reset(); + _commitHeadIndex = 0; + _currentWriteLength = 0; + _length = 0; + } + + /// + /// Allocates memory from the pipeline to write into. + /// + /// The minimum size buffer to allocate + internal Memory GetMemory(int minimumSize) + { + if (_writerCompletion.IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException_NoWritingAllowed(); + } + + if (minimumSize < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.minimumSize); + } + + lock (_sync) + { + BufferSegment segment = _writingHead ?? AllocateWriteHeadUnsynchronized(minimumSize); + + int bytesLeftInBuffer = segment.WritableBytes; + + // If inadequate bytes left or if the segment is readonly + if (bytesLeftInBuffer == 0 || bytesLeftInBuffer < minimumSize || segment.ReadOnly) + { + BufferSegment nextSegment = CreateSegmentUnsynchronized(); + + nextSegment.SetMemory(_pool.Rent(Math.Max(_minimumSegmentSize, minimumSize))); + + segment.SetNext(nextSegment); + + _writingHead = nextSegment; + } + } + + return _writingHead.AvailableMemory.Slice(_writingHead.End, _writingHead.WritableBytes); + } + + internal Span GetSpan(int minimumSize) => GetMemory(minimumSize).Span; + + private BufferSegment AllocateWriteHeadUnsynchronized(int count) + { + BufferSegment segment = null; + + if (_commitHead != null && !_commitHead.ReadOnly) + { + // Try to return the tail so the calling code can append to it + int remaining = _commitHead.WritableBytes; + + if (count <= remaining && remaining > 0) + { + // Free tail space of the right amount, use that + segment = _commitHead; + } + } + + if (segment == null) + { + // No free tail space, allocate a new segment + segment = CreateSegmentUnsynchronized(); + segment.SetMemory(_pool.Rent(Math.Max(_minimumSegmentSize, count))); + } + + if (_commitHead == null) + { + // No previous writes have occurred + _commitHead = segment; + } + else if (segment != _commitHead && _commitHead.Next == null) + { + // Append the segment to the commit head if writes have been committed + // and it isn't the same segment (unused tail space) + _commitHead.SetNext(segment); + } + + // Set write head to assigned segment + _writingHead = segment; + + return segment; + } + + private BufferSegment CreateSegmentUnsynchronized() + { + if (_pooledSegmentCount > 0) + { + _pooledSegmentCount--; + return _bufferSegmentPool[_pooledSegmentCount]; + } + + return new BufferSegment(); + } + + private void ReturnSegmentUnsynchronized(BufferSegment segment) + { + if (_pooledSegmentCount < _bufferSegmentPool.Length) + { + _bufferSegmentPool[_pooledSegmentCount] = segment; + _pooledSegmentCount++; + } + } + + internal void Commit() + { + // Changing commit head shared with Reader + lock (_sync) + { + CommitUnsynchronized(); + } + } + + internal void CommitUnsynchronized() + { + if (_writingHead == null) + { + // Nothing written to commit + return; + } + + if (_readHead == null) + { + // Update the head to point to the head of the buffer. + // This happens if we called alloc(0) then write + _readHead = _commitHead; + _readHeadIndex = 0; + } + + // Always move the commit head to the write head + _commitHead = _writingHead; + _commitHeadIndex = _writingHead.End; + _length += _currentWriteLength; + + // Do not reset if reader is complete + if (_maximumSizeHigh > 0 && + _length >= _maximumSizeHigh && + !_readerCompletion.IsCompleted) + { + _writerAwaitable.Reset(); + } + + // Clear the writing state + _writingHead = null; + _currentWriteLength = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Advance(int bytesWritten) + { + if (_writingHead == null) + { + ThrowHelper.ThrowInvalidOperationException_NotWritingNoAlloc(); + } + + if (bytesWritten > 0) + { + Debug.Assert(!_writingHead.ReadOnly); + Debug.Assert(_writingHead.Next == null); + + Memory buffer = _writingHead.AvailableMemory; + int bufferIndex = _writingHead.End + bytesWritten; + + if (bufferIndex > buffer.Length) + { + ThrowHelper.ThrowInvalidOperationException_AdvancingPastBufferSize(); + } + + _writingHead.End = bufferIndex; + _currentWriteLength += bytesWritten; + } + else if (bytesWritten < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.bytesWritten); + } + + // and if zero, just do nothing; don't need to validate tail etc + } + + internal PipeAwaiter FlushAsync(CancellationToken cancellationToken) + { + Action awaitable; + CancellationTokenRegistration cancellationTokenRegistration; + lock (_sync) + { + if (_writingHead != null) + { + // Commit the data as not already committed + CommitUnsynchronized(); + } + + awaitable = _readerAwaitable.Complete(); + + cancellationTokenRegistration = _writerAwaitable.AttachToken(cancellationToken, _signalWriterAwaitable, this); + } + + cancellationTokenRegistration.Dispose(); + + TrySchedule(_readerScheduler, awaitable); + + return new PipeAwaiter(this); + } + + internal void CompleteWriter(Exception exception) + { + Action awaitable; + PipeCompletionCallbacks completionCallbacks; + bool readerCompleted; + + lock (_sync) + { + if (_currentWriteLength > 0) + { + ThrowHelper.ThrowInvalidOperationException_CompleteWriterActiveWriter(); + } + + completionCallbacks = _writerCompletion.TryComplete(exception); + awaitable = _readerAwaitable.Complete(); + readerCompleted = _readerCompletion.IsCompleted; + } + + if (completionCallbacks != null) + { + TrySchedule(_readerScheduler, _invokeCompletionCallbacks, completionCallbacks); + } + + TrySchedule(_readerScheduler, awaitable); + + if (readerCompleted) + { + CompletePipe(); + } + } + + internal void AdvanceReader(SequencePosition consumed) + { + AdvanceReader(consumed, consumed); + } + + internal void AdvanceReader(SequencePosition consumed, SequencePosition examined) + { + BufferSegment returnStart = null; + BufferSegment returnEnd = null; + + Action continuation = null; + lock (_sync) + { + var examinedEverything = false; + if (examined.Segment == _commitHead) + { + examinedEverything = _commitHead != null ? examined.Index == _commitHeadIndex - _commitHead.Start : examined.Index == 0; + } + + if (consumed.Segment != null) + { + if (_readHead == null) + { + ThrowHelper.ThrowInvalidOperationException_AdvanceToInvalidCursor(); + return; + } + + var consumedSegment = (BufferSegment)consumed.Segment; + + returnStart = _readHead; + returnEnd = consumedSegment; + + // Check if we crossed _maximumSizeLow and complete backpressure + long consumedBytes = new ReadOnlySequence(returnStart, _readHeadIndex, consumedSegment, consumed.Index).Length; + long oldLength = _length; + _length -= consumedBytes; + + if (oldLength >= _maximumSizeLow && + _length < _maximumSizeLow) + { + continuation = _writerAwaitable.Complete(); + } + + // Check if we consumed entire last segment + // if we are going to return commit head we need to check that there is no writing operation that + // might be using tailspace + if (consumed.Index == returnEnd.Length && _writingHead != returnEnd) + { + var nextBlock = returnEnd.NextSegment; + if (_commitHead == returnEnd) + { + _commitHead = nextBlock; + _commitHeadIndex = 0; + } + + _readHead = nextBlock; + _readHeadIndex = 0; + returnEnd = nextBlock; + } + else + { + _readHead = consumedSegment; + _readHeadIndex = consumed.Index; + } + } + + // We reset the awaitable to not completed if we've examined everything the producer produced so far + // but only if writer is not completed yet + if (examinedEverything && !_writerCompletion.IsCompleted) + { + // Prevent deadlock where reader awaits new data and writer await backpressure + if (!_writerAwaitable.IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException_BackpressureDeadlock(); + } + _readerAwaitable.Reset(); + } + + _readingState.End(); + + while (returnStart != null && returnStart != returnEnd) + { + returnStart.ResetMemory(); + ReturnSegmentUnsynchronized(returnStart); + returnStart = returnStart.NextSegment; + } + } + + TrySchedule(_writerScheduler, continuation); + } + + internal void CompleteReader(Exception exception) + { + PipeCompletionCallbacks completionCallbacks; + Action awaitable; + bool writerCompleted; + + lock (_sync) + { + if (_readingState.IsActive) + { + ThrowHelper.ThrowInvalidOperationException_CompleteReaderActiveReader(); + } + + completionCallbacks = _readerCompletion.TryComplete(exception); + awaitable = _writerAwaitable.Complete(); + writerCompleted = _writerCompletion.IsCompleted; + } + + if (completionCallbacks != null) + { + TrySchedule(_writerScheduler, _invokeCompletionCallbacks, completionCallbacks); + } + + TrySchedule(_writerScheduler, awaitable); + + if (writerCompleted) + { + CompletePipe(); + } + } + + internal void OnWriterCompleted(Action callback, object state) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + PipeCompletionCallbacks completionCallbacks; + lock (_sync) + { + completionCallbacks = _writerCompletion.AddCallback(callback, state); + } + + if (completionCallbacks != null) + { + TrySchedule(_readerScheduler, _invokeCompletionCallbacks, completionCallbacks); + } + } + + /// + /// Cancel to currently pending call to without completing the . + /// + internal void CancelPendingRead() + { + Action awaitable; + lock (_sync) + { + awaitable = _readerAwaitable.Cancel(); + } + TrySchedule(_readerScheduler, awaitable); + } + + /// + /// Cancel to currently pending call to without completing the . + /// + internal void CancelPendingFlush() + { + Action awaitable; + lock (_sync) + { + awaitable = _writerAwaitable.Cancel(); + } + TrySchedule(_writerScheduler, awaitable); + } + + internal void OnReaderCompleted(Action callback, object state) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + PipeCompletionCallbacks completionCallbacks; + lock (_sync) + { + completionCallbacks = _readerCompletion.AddCallback(callback, state); + } + + if (completionCallbacks != null) + { + TrySchedule(_writerScheduler, _invokeCompletionCallbacks, completionCallbacks); + } + } + + internal PipeAwaiter ReadAsync(CancellationToken token) + { + CancellationTokenRegistration cancellationTokenRegistration; + if (_readerCompletion.IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed(); + } + lock (_sync) + { + cancellationTokenRegistration = _readerAwaitable.AttachToken(token, _signalReaderAwaitable, this); + } + cancellationTokenRegistration.Dispose(); + return new PipeAwaiter(this); + } + + internal bool TryRead(out ReadResult result) + { + lock (_sync) + { + if (_readerCompletion.IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed(); + } + + result = new ReadResult(); + if (_length > 0 || _readerAwaitable.IsCompleted) + { + GetResult(ref result); + return true; + } + + if (_readerAwaitable.HasContinuation) + { + ThrowHelper.ThrowInvalidOperationException_AlreadyReading(); + } + return false; + } + } + + private static void TrySchedule(PipeScheduler scheduler, Action action) + { + if (action != null) + { + scheduler.Schedule(action); + } + } + + private static void TrySchedule(PipeScheduler scheduler, Action action, object state) + { + if (action != null) + { + scheduler.Schedule(action, state); + } + } + + private void CompletePipe() + { + lock (_sync) + { + if (_disposed) + { + return; + } + + _disposed = true; + // Return all segments + // if _readHead is null we need to try return _commitHead + // because there might be a block allocated for writing + BufferSegment segment = _readHead ?? _commitHead; + while (segment != null) + { + BufferSegment returnSegment = segment; + segment = segment.NextSegment; + + returnSegment.ResetMemory(); + } + + _writingHead = null; + _readHead = null; + _commitHead = null; + } + } + + bool IPipeAwaiter.IsCompleted => _readerAwaitable.IsCompleted; + + void IPipeAwaiter.OnCompleted(Action continuation) + { + Action awaitable; + bool doubleCompletion; + lock (_sync) + { + awaitable = _readerAwaitable.OnCompleted(continuation, out doubleCompletion); + } + if (doubleCompletion) + { + Writer.Complete(ThrowHelper.CreateInvalidOperationException_NoConcurrentOperation()); + } + TrySchedule(_readerScheduler, awaitable); + } + + ReadResult IPipeAwaiter.GetResult() + { + if (!_readerAwaitable.IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException_GetResultNotCompleted(); + } + + var result = new ReadResult(); + lock (_sync) + { + GetResult(ref result); + } + return result; + } + + private void GetResult(ref ReadResult result) + { + if (_writerCompletion.IsCompletedOrThrow()) + { + result.ResultFlags |= ResultFlags.Completed; + } + + bool isCanceled = _readerAwaitable.ObserveCancelation(); + if (isCanceled) + { + result.ResultFlags |= ResultFlags.Canceled; + } + + // No need to read end if there is no head + BufferSegment head = _readHead; + + if (head != null) + { + // Reading commit head shared with writer + result.ResultBuffer = new ReadOnlySequence(head, _readHeadIndex, _commitHead, _commitHeadIndex - _commitHead.Start); + } + + if (isCanceled) + { + _readingState.BeginTentative(); + } + else + { + _readingState.Begin(); + } + } + + bool IPipeAwaiter.IsCompleted => _writerAwaitable.IsCompleted; + + FlushResult IPipeAwaiter.GetResult() + { + var result = new FlushResult(); + lock (_sync) + { + if (!_writerAwaitable.IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException_GetResultNotCompleted(); + } + + // Change the state from to be canceled -> observed + if (_writerAwaitable.ObserveCancelation()) + { + result.ResultFlags |= ResultFlags.Canceled; + } + if (_readerCompletion.IsCompletedOrThrow()) + { + result.ResultFlags |= ResultFlags.Completed; + } + } + + return result; + } + + void IPipeAwaiter.OnCompleted(Action continuation) + { + Action awaitable; + bool doubleCompletion; + lock (_sync) + { + awaitable = _writerAwaitable.OnCompleted(continuation, out doubleCompletion); + } + if (doubleCompletion) + { + Reader.Complete(ThrowHelper.CreateInvalidOperationException_NoConcurrentOperation()); + } + TrySchedule(_writerScheduler, awaitable); + } + + private void ReaderCancellationRequested() + { + Action action; + lock (_sync) + { + action = _readerAwaitable.Cancel(); + } + TrySchedule(_readerScheduler, action); + } + + private void WriterCancellationRequested() + { + Action action; + lock (_sync) + { + action = _writerAwaitable.Cancel(); + } + TrySchedule(_writerScheduler, action); + } + + public PipeReader Reader { get; } + + public PipeWriter Writer { get; } + + public void Reset() + { + lock (_sync) + { + if (!_disposed) + { + throw new InvalidOperationException("Both reader and writer need to be completed to be able to reset "); + } + + _disposed = false; + ResetState(); + } + } + + private sealed class DefaultPipeReader : PipeReader + { + private readonly Pipe _pipe; + + public DefaultPipeReader(Pipe pipe) + { + _pipe = pipe; + } + + public override bool TryRead(out ReadResult result) + { + return _pipe.TryRead(out result); + } + + public override PipeAwaiter ReadAsync(CancellationToken cancellationToken = default) + { + return _pipe.ReadAsync(cancellationToken); + } + + public override void AdvanceTo(SequencePosition consumed) + { + _pipe.AdvanceReader(consumed); + } + + public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) + { + _pipe.AdvanceReader(consumed, examined); + } + + public override void CancelPendingRead() + { + _pipe.CancelPendingRead(); + } + + public override void Complete(Exception exception = null) + { + _pipe.CompleteReader(exception); + } + + public override void OnWriterCompleted(Action callback, object state) + { + _pipe.OnWriterCompleted(callback, state); + } + } + + private sealed class DefaultPipeWriter : PipeWriter + { + private readonly Pipe _pipe; + + public DefaultPipeWriter(Pipe pipe) + { + _pipe = pipe; + } + + public override void Complete(Exception exception = null) + { + _pipe.CompleteWriter(exception); + } + + public override void CancelPendingFlush() + { + _pipe.CancelPendingFlush(); + } + + public override void OnReaderCompleted(Action callback, object state) + { + _pipe.OnReaderCompleted(callback, state); + } + + public override PipeAwaiter FlushAsync(CancellationToken cancellationToken = default) + { + return _pipe.FlushAsync(cancellationToken); + } + + public override void Commit() + { + _pipe.Commit(); + } + + public override void Advance(int bytes) + { + _pipe.Advance(bytes); + } + + public override Memory GetMemory(int minimumLength = 0) + { + return _pipe.GetMemory(minimumLength); + } + + public override Span GetSpan(int minimumLength = 0) + { + return _pipe.GetSpan(minimumLength); + } + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs new file mode 100644 index 000000000000..b0fcb397e8e6 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs @@ -0,0 +1,150 @@ +// 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.Runtime.CompilerServices; +using System.Threading; + +namespace System.IO.Pipelines +{ + internal struct PipeAwaitable + { + private static readonly Action _awaitableIsCompleted = () => { }; + private static readonly Action _awaitableIsNotCompleted = () => { }; + + private CanceledState _canceledState; + private Action _state; + private CancellationToken _cancellationToken; + private CancellationTokenRegistration _cancellationTokenRegistration; + + public PipeAwaitable(bool completed) + { + _canceledState = CanceledState.NotCanceled; + _state = completed ? _awaitableIsCompleted : _awaitableIsNotCompleted; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CancellationTokenRegistration AttachToken(CancellationToken cancellationToken, Action callback, object state) + { + CancellationTokenRegistration oldRegistration; + if (!cancellationToken.Equals(_cancellationToken)) + { + oldRegistration = _cancellationTokenRegistration; + _cancellationToken = cancellationToken; + if (_cancellationToken.CanBeCanceled) + { + _cancellationToken.ThrowIfCancellationRequested(); + _cancellationTokenRegistration = _cancellationToken.Register(callback, state); + } + } + return oldRegistration; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Action Complete() + { + Action awaitableState = _state; + _state = _awaitableIsCompleted; + + if (!ReferenceEquals(awaitableState, _awaitableIsCompleted) && + !ReferenceEquals(awaitableState, _awaitableIsNotCompleted)) + { + return awaitableState; + } + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + if (ReferenceEquals(_state, _awaitableIsCompleted) && + _canceledState < CanceledState.CancellationPreRequested) + { + _state = _awaitableIsNotCompleted; + } + + // Change the state from observed -> not cancelled. + // We only want to reset the cancelled state if it was observed + if (_canceledState == CanceledState.CancellationObserved) + { + _canceledState = CanceledState.NotCanceled; + } + } + + public bool IsCompleted => ReferenceEquals(_state, _awaitableIsCompleted); + internal bool HasContinuation => !ReferenceEquals(_state, _awaitableIsNotCompleted); + + public Action OnCompleted(Action continuation, out bool doubleCompletion) + { + doubleCompletion = false; + Action awaitableState = _state; + if (ReferenceEquals(awaitableState, _awaitableIsNotCompleted)) + { + _state = continuation; + } + + if (ReferenceEquals(awaitableState, _awaitableIsCompleted)) + { + return continuation; + } + + if (!ReferenceEquals(awaitableState, _awaitableIsNotCompleted)) + { + doubleCompletion = true; + return continuation; + } + + return null; + } + + public Action Cancel() + { + Action action = Complete(); + _canceledState = action == null ? + CanceledState.CancellationPreRequested : + CanceledState.CancellationRequested; + return action; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ObserveCancelation() + { + if (_canceledState == CanceledState.NotCanceled) + { + return false; + } + + bool isPrerequested = _canceledState == CanceledState.CancellationPreRequested; + + if (_canceledState >= CanceledState.CancellationPreRequested) + { + _canceledState = CanceledState.CancellationObserved; + + // Do not reset awaitable if we were not awaiting in the first place + if (!isPrerequested) + { + Reset(); + } + + _cancellationToken.ThrowIfCancellationRequested(); + + return true; + } + + return false; + } + + public override string ToString() + { + return $"CancelledState: {_canceledState}, {nameof(IsCompleted)}: {IsCompleted}"; + } + + private enum CanceledState + { + NotCanceled = 0, + CancellationObserved = 1, + CancellationPreRequested = 2, + CancellationRequested = 3, + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaiter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaiter.cs new file mode 100644 index 000000000000..2c96a97ec9d5 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaiter.cs @@ -0,0 +1,41 @@ +// 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.Runtime.CompilerServices; + +namespace System.IO.Pipelines +{ + /// + /// An awaitable object that represents an asynchronous read operation + /// + public struct PipeAwaiter : ICriticalNotifyCompletion + { + private readonly IPipeAwaiter _awaiter; + + public PipeAwaiter(IPipeAwaiter awaiter) + { + _awaiter = awaiter; + } + /// + /// Gets whether the async operation being awaited is completed. + /// + public bool IsCompleted => _awaiter.IsCompleted; + + /// + /// Ends the await on the completed . + /// + public T GetResult() => _awaiter.GetResult(); + + /// + /// Gets an awaiter used to await this . + /// + public PipeAwaiter GetAwaiter() => this; + + /// + public void UnsafeOnCompleted(Action continuation) => _awaiter.OnCompleted(continuation); + + /// + public void OnCompleted(Action continuation) => _awaiter.OnCompleted(continuation); + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs new file mode 100644 index 000000000000..321554bb765b --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs @@ -0,0 +1,117 @@ +// 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.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; + +namespace System.IO.Pipelines +{ + internal struct PipeCompletion + { + private static readonly ArrayPool CompletionCallbackPool = ArrayPool.Shared; + + private const int InitialCallbacksSize = 1; + private static readonly Exception _completedNoException = new Exception(); + + private Exception _exception; + + private PipeCompletionCallback[] _callbacks; + private int _callbackCount; + + public bool IsCompleted => _exception != null; + + public PipeCompletionCallbacks TryComplete(Exception exception = null) + { + if (_exception == null) + { + // Set the exception object to the exception passed in or a sentinel value + _exception = exception ?? _completedNoException; + } + return GetCallbacks(); + } + + public PipeCompletionCallbacks AddCallback(Action callback, object state) + { + if (_callbacks == null) + { + _callbacks = CompletionCallbackPool.Rent(InitialCallbacksSize); + } + + int newIndex = _callbackCount; + _callbackCount++; + + if (newIndex == _callbacks.Length) + { + PipeCompletionCallback[] newArray = CompletionCallbackPool.Rent(_callbacks.Length * 2); + Array.Copy(_callbacks, newArray, _callbacks.Length); + CompletionCallbackPool.Return(_callbacks, clearArray: true); + _callbacks = newArray; + } + + _callbacks[newIndex].Callback = callback; + _callbacks[newIndex].State = state; + + if (IsCompleted) + { + return GetCallbacks(); + } + + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsCompletedOrThrow() + { + if (_exception == null) + { + return false; + } + + if (_exception != _completedNoException) + { + ThrowFailed(); + } + + return true; + } + + private PipeCompletionCallbacks GetCallbacks() + { + Debug.Assert(IsCompleted); + if (_callbackCount == 0) + { + return null; + } + + var callbacks = new PipeCompletionCallbacks(CompletionCallbackPool, + _callbackCount, + _exception == _completedNoException ? null : _exception, + _callbacks); + + _callbacks = null; + _callbackCount = 0; + return callbacks; + } + + public void Reset() + { + Debug.Assert(IsCompleted); + Debug.Assert(_callbacks == null); + _exception = null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowFailed() + { + ExceptionDispatchInfo.Capture(_exception).Throw(); + } + + public override string ToString() + { + return $"{nameof(IsCompleted)}: {IsCompleted}"; + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallback.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallback.cs new file mode 100644 index 000000000000..6647ad6b39a6 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallback.cs @@ -0,0 +1,12 @@ +// 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. + +namespace System.IO.Pipelines +{ + internal struct PipeCompletionCallback + { + public Action Callback; + public object State; + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallbacks.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallbacks.cs new file mode 100644 index 000000000000..371e5e434b4e --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallbacks.cs @@ -0,0 +1,64 @@ +// 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.Buffers; +using System.Collections.Generic; + +namespace System.IO.Pipelines +{ + internal class PipeCompletionCallbacks + { + private readonly ArrayPool _pool; + private readonly int _count; + private readonly Exception _exception; + private readonly PipeCompletionCallback[] _callbacks; + + public PipeCompletionCallbacks(ArrayPool pool, int count, Exception exception, PipeCompletionCallback[] callbacks) + { + _pool = pool; + _count = count; + _exception = exception; + _callbacks = callbacks; + } + + public void Execute() + { + if (_callbacks == null || _count == 0) + { + return; + } + + try + { + List exceptions = null; + + for (var i = 0; i < _count; i++) + { + PipeCompletionCallback callback = _callbacks[i]; + try + { + callback.Callback(_exception, callback.State); + } + catch (Exception ex) + { + if (exceptions == null) + { + exceptions = new List(); + } + exceptions.Add(ex); + } + } + + if (exceptions != null) + { + throw new AggregateException(exceptions); + } + } + finally + { + _pool.Return(_callbacks, clearArray: true); + } + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs new file mode 100644 index 000000000000..7303eebedd70 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs @@ -0,0 +1,68 @@ +// 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.Buffers; + +namespace System.IO.Pipelines +{ + /// + /// Represents a set of options + /// + public class PipeOptions + { + /// + /// Default instance of + /// + public static PipeOptions Default { get; } = new PipeOptions(); + + /// + /// Creates a new instance of + /// + public PipeOptions( + MemoryPool pool = null, + PipeScheduler readerScheduler = null, + PipeScheduler writerScheduler = null, + long pauseWriterThreshold = 0, + long resumeWriterThreshold = 0, + int minimumSegmentSize = 2048) + { + Pool = pool ?? MemoryPool.Shared; + ReaderScheduler = readerScheduler; + WriterScheduler = writerScheduler; + PauseWriterThreshold = pauseWriterThreshold; + ResumeWriterThreshold = resumeWriterThreshold; + MinimumSegmentSize = minimumSegmentSize; + } + + /// + /// Gets amount of bytes in when starts blocking + /// + public long PauseWriterThreshold { get; } + + /// + /// Gets amount of bytes in when stops blocking + /// + public long ResumeWriterThreshold { get; } + + /// + /// Gets minimum size of segment requested from + /// + public int MinimumSegmentSize { get; } + + /// + /// Gets the used to execute callbacks + /// + public PipeScheduler WriterScheduler { get; } + + /// + /// Gets the used to execute callbacks + /// + public PipeScheduler ReaderScheduler { get; } + + /// + /// Gets the instances used for buffer management + /// + public MemoryPool Pool { get; } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReader.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReader.cs new file mode 100644 index 000000000000..a66eaf793819 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReader.cs @@ -0,0 +1,65 @@ +// 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.Threading; + +namespace System.IO.Pipelines +{ + /// + /// Defines a class that provides access to a read side of pipe. + /// + public abstract class PipeReader + { + /// + /// Attempt to synchronously read data the . + /// + /// The + /// True if data was available, or if the call was canceled or the writer was completed. + /// If the pipe returns false, there's no need to call . + public abstract bool TryRead(out ReadResult result); + + /// + /// Asynchronously reads a sequence of bytes from the current . + /// + /// A representing the asynchronous read operation. + public abstract PipeAwaiter ReadAsync(CancellationToken cancellationToken = default); + + /// + /// Moves forward the pipeline's read cursor to after the consumed data. + /// + /// Marks the extent of the data that has been successfully processed. + /// + /// The memory for the consumed data will be released and no longer available. + /// The examined data communicates to the pipeline when it should signal more data is available. + /// + public abstract void AdvanceTo(SequencePosition consumed); + + /// + /// Moves forward the pipeline's read cursor to after the consumed data. + /// + /// Marks the extent of the data that has been successfully processed. + /// Marks the extent of the data that has been read and examined. + /// + /// The memory for the consumed data will be released and no longer available. + /// The examined data communicates to the pipeline when it should signal more data is available. + /// + public abstract void AdvanceTo(SequencePosition consumed, SequencePosition examined); + + /// + /// Cancel to currently pending or if none is pending next call to , without completing the . + /// + public abstract void CancelPendingRead(); + + /// + /// Signal to the producer that the consumer is done reading. + /// + /// Optional indicating a failure that's causing the pipeline to complete. + public abstract void Complete(Exception exception = null); + + /// + /// Cancel the pending operation. If there is none, cancels next operation, without completing the . + /// + public abstract void OnWriterCompleted(Action callback, object state); + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs new file mode 100644 index 000000000000..2a8e195be70c --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.IO.Pipelines +{ + internal struct PipeReaderState + { + private State _state; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Begin() + { + // Inactive and Tentative are allowed + if (_state == State.Active) + { + ThrowHelper.ThrowInvalidOperationException_AlreadyReading(); + } + + _state = State.Active; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void BeginTentative() + { + // Inactive and Tentative are allowed + if (_state == State.Active) + { + ThrowHelper.ThrowInvalidOperationException_AlreadyReading(); + } + + _state = State.Tentative; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void End() + { + if (_state == State.Inactive) + { + ThrowHelper.CreateInvalidOperationException_NoReadToComplete(); + } + + _state = State.Inactive; + } + + public bool IsActive => _state == State.Active; + + public override string ToString() + { + return $"State: {_state}"; + } + } + + internal enum State: byte + { + Inactive = 1, + Active = 2, + Tentative = 3 + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs new file mode 100644 index 000000000000..889e33d4e7b7 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs @@ -0,0 +1,35 @@ +// 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. + +namespace System.IO.Pipelines +{ + /// + /// Abstraction for running and callbacks and continuations + /// + public abstract class PipeScheduler + { + private static readonly ThreadPoolScheduler _threadPoolScheduler = new ThreadPoolScheduler(); + private static readonly InlineScheduler _inlineScheduler = new InlineScheduler(); + + /// + /// The implementation that queues callbacks to thread pool + /// + public static PipeScheduler ThreadPool => _threadPoolScheduler; + + /// + /// The implementation that runs callbacks inline + /// + public static PipeScheduler Inline => _inlineScheduler; + + /// + /// Requests to be run on scheduler + /// + public abstract void Schedule(Action action); + + /// + /// Requests to be run on scheduler with being passed in + /// + public abstract void Schedule(Action action, object state); + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs new file mode 100644 index 000000000000..a04140d010eb --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Threading; + +namespace System.IO.Pipelines +{ + /// + /// Defines a class that provides a pipeline to which data can be written. + /// + public abstract class PipeWriter : IBufferWriter + { + /// + /// Marks the as being complete, meaning no more items will be written to it. + /// + /// Optional indicating a failure that's causing the pipeline to complete. + public abstract void Complete(Exception exception = null); + + /// + /// Cancel the pending operation. If there is none, cancels next operation, without completing the . + /// + public abstract void CancelPendingFlush(); + + /// + /// Registers a callback that gets executed when the side of the pipe is completed + /// + public abstract void OnReaderCompleted(Action callback, object state); + + /// + /// Makes bytes written available to and runs continuation. + /// + public abstract PipeAwaiter FlushAsync(CancellationToken cancellationToken = default); + + /// + /// Makes bytes written available to without running continuation. + /// + public abstract void Commit(); + + /// + public abstract void Advance(int bytes); + + /// + public abstract Memory GetMemory(int minimumLength = 0); + + /// + public abstract Span GetSpan(int minimumLength = 0); + + /// + /// Writes to the pipe and makes data accessible to + /// + public virtual PipeAwaiter WriteAsync(ReadOnlyMemory source) + { + this.Write(source.Span); + return FlushAsync(); + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs new file mode 100644 index 000000000000..d45a9e4889e5 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; + +namespace System.IO.Pipelines +{ + /// + /// The result of a call. + /// + public struct ReadResult + { + internal ReadOnlySequence ResultBuffer; + internal ResultFlags ResultFlags; + + /// + /// Creates a new instance of setting and flags + /// + public ReadResult(ReadOnlySequence buffer, bool isCanceled, bool isCompleted) + { + ResultBuffer = buffer; + ResultFlags = ResultFlags.None; + + if (isCompleted) + { + ResultFlags |= ResultFlags.Completed; + } + if (isCanceled) + { + ResultFlags |= ResultFlags.Canceled; + } + } + + /// + /// The that was read + /// + public ReadOnlySequence Buffer => ResultBuffer; + + /// + /// True if the current operation was canceled, otherwise false. + /// + public bool IsCanceled => (ResultFlags & ResultFlags.Canceled) != 0; + + /// + /// True if the is complete + /// + public bool IsCompleted => (ResultFlags & ResultFlags.Completed) != 0; + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ResultFlags.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ResultFlags.cs new file mode 100644 index 000000000000..9d7a26ae8aff --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ResultFlags.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.IO.Pipelines +{ + [Flags] + internal enum ResultFlags : byte + { + None = 0, + Canceled = 1, + Completed = 2 + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/SpanLiteralExtensions.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/SpanLiteralExtensions.cs new file mode 100644 index 000000000000..128283b7d821 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/SpanLiteralExtensions.cs @@ -0,0 +1,72 @@ +// 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.Text; + +namespace System.IO.Pipelines +{ + internal class SpanLiteralExtensions + { + internal static void AppendAsLiteral(ReadOnlySpan span, StringBuilder sb) + { + for (int i = 0; i < span.Length; i++) + { + AppendCharLiteral((char) span[i], sb); + } + } + + internal static void AppendCharLiteral(char c, StringBuilder sb) + { + switch (c) + { + case '\'': + sb.Append(@"\'"); + break; + case '\"': + sb.Append("\\\""); + break; + case '\\': + sb.Append(@"\\"); + break; + case '\0': + sb.Append(@"\0"); + break; + case '\a': + sb.Append(@"\a"); + break; + case '\b': + sb.Append(@"\b"); + break; + case '\f': + sb.Append(@"\f"); + break; + case '\n': + sb.Append(@"\n"); + break; + case '\r': + sb.Append(@"\r"); + break; + case '\t': + sb.Append(@"\t"); + break; + case '\v': + sb.Append(@"\v"); + break; + default: + // ASCII printable character + if (!char.IsControl(c)) + { + sb.Append(c); + // As UTF16 escaped character + } + else + { + sb.Append(@"\u"); + sb.Append(((int) c).ToString("x4")); + } + break; + } + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.cs new file mode 100644 index 000000000000..fcf233d97d83 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.cs @@ -0,0 +1,56 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace System.IO.Pipelines +{ + internal sealed class ThreadPoolScheduler : PipeScheduler + { + public override void Schedule(Action action) + { +#if NETCOREAPP2_1 + // Queue to low contention local ThreadPool queue; rather than global queue as per Task + System.Threading.ThreadPool.QueueUserWorkItem(_actionWaitCallback, action, preferLocal: true); +#elif NETSTANDARD2_0 + System.Threading.ThreadPool.QueueUserWorkItem(_actionWaitCallback, action); +#else + Task.Factory.StartNew(action); +#endif + } + + public override void Schedule(Action action, object state) + { +#if NETCOREAPP2_1 + // Queue to low contention local ThreadPool queue; rather than global queue as per Task + System.Threading.ThreadPool.QueueUserWorkItem(_actionObjectWaitCallback, new ActionObjectAsWaitCallback(action, state), preferLocal: true); +#elif NETSTANDARD2_0 + System.Threading.ThreadPool.QueueUserWorkItem(_actionObjectWaitCallback, new ActionObjectAsWaitCallback(action, state)); +#else + Task.Factory.StartNew(action, state); +#endif + } + +#if NETCOREAPP2_1 || NETSTANDARD2_0 + private readonly static WaitCallback _actionWaitCallback = state => ((Action)state)(); + + private readonly static WaitCallback _actionObjectWaitCallback = state => ((ActionObjectAsWaitCallback)state).Run(); + + private sealed class ActionObjectAsWaitCallback + { + private Action _action; + private object _state; + + public ActionObjectAsWaitCallback(Action action, object state) + { + _action = action; + _state = state; + } + + public void Run() => _action(_state); + } +#endif + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs new file mode 100644 index 000000000000..475dd13348df --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs @@ -0,0 +1,154 @@ +// 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.Runtime.CompilerServices; + +namespace System.IO.Pipelines +{ + internal class ThrowHelper + { + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) { throw CreateArgumentOutOfRangeException(argument); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static Exception CreateArgumentOutOfRangeException(ExceptionArgument argument) { return new ArgumentOutOfRangeException(argument.ToString()); } + + public static void ThrowInvalidOperationException_NotWritingNoAlloc() + { + throw CreateInvalidOperationException_NotWritingNoAlloc(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_NotWritingNoAlloc() + { + return new InvalidOperationException("No writing operation. Make sure GetMemory() was called."); + } + + public static void ThrowInvalidOperationException_AlreadyReading() + { + throw CreateInvalidOperationException_AlreadyReading(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_AlreadyReading() + { + return new InvalidOperationException("Already reading."); + } + + public static void ThrowInvalidOperationException_NoReadToComplete() + { + throw CreateInvalidOperationException_NoReadToComplete(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_NoReadToComplete() + { + return new InvalidOperationException("No reading operation to complete."); + } + + public static void ThrowInvalidOperationException_NoConcurrentOperation() + { + throw CreateInvalidOperationException_NoConcurrentOperation(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_NoConcurrentOperation() + { + return new InvalidOperationException("Concurrent reads or writes are not supported."); + } + + public static void ThrowInvalidOperationException_GetResultNotCompleted() + { + throw CreateInvalidOperationException_GetResultNotCompleted(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_GetResultNotCompleted() + { + return new InvalidOperationException("Can't GetResult unless completed"); + } + + public static void ThrowInvalidOperationException_NoWritingAllowed() + { + throw CreateInvalidOperationException_NoWritingAllowed(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_NoWritingAllowed() + { + return new InvalidOperationException("Writing is not allowed after writer was completed"); + } + + public static void ThrowInvalidOperationException_NoReadingAllowed() + { + throw CreateInvalidOperationException_NoReadingAllowed(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + + public static Exception CreateInvalidOperationException_NoReadingAllowed() + { + return new InvalidOperationException("Reading is not allowed after reader was completed"); + } + + public static void ThrowInvalidOperationException_CompleteWriterActiveWriter() + { + throw CreateInvalidOperationException_CompleteWriterActiveWriter(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_CompleteWriterActiveWriter() + { + return new InvalidOperationException("Can't complete writer while writing."); + } + + public static void ThrowInvalidOperationException_CompleteReaderActiveReader() + { + throw CreateInvalidOperationException_CompleteReaderActiveReader(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_CompleteReaderActiveReader() + { + return new InvalidOperationException("Can't complete reader while reading."); + } + + public static void ThrowInvalidOperationException_AdvancingPastBufferSize() + { + throw CreateInvalidOperationException_AdvancingPastBufferSize(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_AdvancingPastBufferSize() + { + return new InvalidOperationException("Can't advance past buffer size"); + } + + public static void ThrowInvalidOperationException_BackpressureDeadlock() + { + throw CreateInvalidOperationException_BackpressureDeadlock(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_BackpressureDeadlock() + { + return new InvalidOperationException("Advancing examined to the end would cause pipe to deadlock because FlushAsync is waiting"); + } + + public static void ThrowInvalidOperationException_AdvanceToInvalidCursor() + { + throw CreateInvalidOperationException_AdvanceToInvalidCursor(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_AdvanceToInvalidCursor() + { + return new InvalidOperationException("Pipe is already advanced past provided cursor"); + } + } + + internal enum ExceptionArgument + { + minimumSize, + bytesWritten + } +} diff --git a/src/System.IO.Pipelines/tests/Configurations.props b/src/System.IO.Pipelines/tests/Configurations.props new file mode 100644 index 000000000000..78953dfc8851 --- /dev/null +++ b/src/System.IO.Pipelines/tests/Configurations.props @@ -0,0 +1,8 @@ + + + + + netstandard; + + + diff --git a/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj b/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj new file mode 100644 index 000000000000..fdc1061d4152 --- /dev/null +++ b/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj @@ -0,0 +1,12 @@ + + + + + {9E984EB2-827E-4029-9647-FB5F8B67C553} + + + + + + + \ No newline at end of file diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj index a7afd9c0fec3..31f7f45b0cf4 100644 --- a/src/System.Memory/src/System.Memory.csproj +++ b/src/System.Memory/src/System.Memory.csproj @@ -30,6 +30,7 @@ + diff --git a/src/System.Memory/src/System/Buffers/IBufferWriter.cs b/src/System.Memory/src/System/Buffers/IBufferWriter.cs new file mode 100644 index 000000000000..6883deb38019 --- /dev/null +++ b/src/System.Memory/src/System/Buffers/IBufferWriter.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Buffers +{ + /// + /// Represents a sink + /// + public interface IBufferWriter + { + /// + /// Notifies that amount of data was written to / + /// + void Advance(int bytes); + + /// + /// Requests the of at least in size. + /// If is equal to 0, currently available memory would get returned. + /// + Memory GetMemory(int minimumLength = 0); + + /// + /// Requests the of at least in size. + /// If is equal to 0, currently available memory would get returned. + /// + Span GetSpan(int minimumLength = 0); + } +} From dee5c346196ea18653db80cf630d2d446311a223 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Thu, 8 Feb 2018 14:19:48 -0800 Subject: [PATCH 02/14] Compiles --- ...g.Channels.sln => System.IO.Pipelines.sln} | 17 +- .../ref/System.IO.Pipelines.cs | 97 +++ .../ref/System.IO.Pipelines.csproj | 5 +- .../src/System.IO.Pipelines.csproj | 3 +- .../src/System/IO/Pipelines/Pipe.cs | 64 +- .../src/System/IO/Pipelines/PipeAwaiter.cs | 3 + .../src/System/IO/Pipelines/PipeReader.cs | 2 +- .../src/System/IO/Pipelines/PipeScheduler.cs | 4 +- .../src/System/IO/Pipelines/PipeWriter.cs | 33 +- .../src/System/IO/Pipelines/ReadResult.cs | 2 +- .../tests/BackpressureTests.cs | 143 +++++ .../tests/FlushAsyncCancellationTests.cs | 320 ++++++++++ .../tests/FlushAsyncCompletionTests.cs | 37 ++ .../tests/FlushResultTests.cs | 24 + .../tests/PipeCompletionCallbacksTests.cs | 472 +++++++++++++++ .../tests/PipeLengthTests.cs | 102 ++++ .../tests/PipePoolTests.cs | 216 +++++++ .../tests/PipeResetTests.cs | 81 +++ src/System.IO.Pipelines/tests/PipeTest.cs | 35 ++ .../tests/PipeWriterTests.cs | 210 +++++++ .../tests/PipelineReaderWriterFacts.cs | 561 ++++++++++++++++++ .../tests/ReadAsyncCancellationTests.cs | 433 ++++++++++++++ .../tests/ReadAsyncCompletionTests.cs | 34 ++ .../tests/ReadResultTests.cs | 27 + .../tests/SchedulerFacts.cs | 204 +++++++ .../tests/System.IO.Pipelines.Tests.csproj | 19 + .../tests/TestMemoryPool.cs | 139 +++++ src/System.Memory/ref/System.Memory.cs | 7 + src/System.Memory/src/System.Memory.csproj | 2 +- ...enceExtensions.cs => BuffersExtensions.cs} | 38 ++ .../src/System/Buffers/IBufferWriter.cs | 10 +- 31 files changed, 3298 insertions(+), 46 deletions(-) rename src/System.IO.Pipelines/{System.Threading.Channels.sln => System.IO.Pipelines.sln} (77%) create mode 100644 src/System.IO.Pipelines/tests/BackpressureTests.cs create mode 100644 src/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs create mode 100644 src/System.IO.Pipelines/tests/FlushAsyncCompletionTests.cs create mode 100644 src/System.IO.Pipelines/tests/FlushResultTests.cs create mode 100644 src/System.IO.Pipelines/tests/PipeCompletionCallbacksTests.cs create mode 100644 src/System.IO.Pipelines/tests/PipeLengthTests.cs create mode 100644 src/System.IO.Pipelines/tests/PipePoolTests.cs create mode 100644 src/System.IO.Pipelines/tests/PipeResetTests.cs create mode 100644 src/System.IO.Pipelines/tests/PipeTest.cs create mode 100644 src/System.IO.Pipelines/tests/PipeWriterTests.cs create mode 100644 src/System.IO.Pipelines/tests/PipelineReaderWriterFacts.cs create mode 100644 src/System.IO.Pipelines/tests/ReadAsyncCancellationTests.cs create mode 100644 src/System.IO.Pipelines/tests/ReadAsyncCompletionTests.cs create mode 100644 src/System.IO.Pipelines/tests/ReadResultTests.cs create mode 100644 src/System.IO.Pipelines/tests/SchedulerFacts.cs create mode 100644 src/System.IO.Pipelines/tests/TestMemoryPool.cs rename src/System.Memory/src/System/Buffers/{SequenceExtensions.cs => BuffersExtensions.cs} (66%) diff --git a/src/System.IO.Pipelines/System.Threading.Channels.sln b/src/System.IO.Pipelines/System.IO.Pipelines.sln similarity index 77% rename from src/System.IO.Pipelines/System.Threading.Channels.sln rename to src/System.IO.Pipelines/System.IO.Pipelines.sln index f85347c9a1fb..17264a346caf 100644 --- a/src/System.IO.Pipelines/System.Threading.Channels.sln +++ b/src/System.IO.Pipelines/System.IO.Pipelines.sln @@ -1,17 +1,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.27325.3006 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Pipelines.Tests", "tests\System.IO.Pipelines.Tests.csproj", "{9E984EB2-827E-4029-9647-FB5F8B67C553}" ProjectSection(ProjectDependencies) = postProject {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} = {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Pipelines.Performance.Tests", "tests\Performance\System.IO.Pipelines.Performance.Tests.csproj", "{11ABE2F8-4FB9-48AC-91AA-D04503059550}" - ProjectSection(ProjectDependencies) = postProject - {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} = {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} - EndProjectSection -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Pipelines", "src\System.IO.Pipelines.csproj", "{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}" ProjectSection(ProjectDependencies) = postProject {9C524CA0-92FF-437B-B568-BCE8A794A69A} = {9C524CA0-92FF-437B-B568-BCE8A794A69A} @@ -35,10 +30,6 @@ Global {9E984EB2-827E-4029-9647-FB5F8B67C553}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU {9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU {9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU - {11ABE2F8-4FB9-48AC-91AA-D04503059550}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU - {11ABE2F8-4FB9-48AC-91AA-D04503059550}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU - {11ABE2F8-4FB9-48AC-91AA-D04503059550}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU - {11ABE2F8-4FB9-48AC-91AA-D04503059550}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU @@ -53,8 +44,10 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {9E984EB2-827E-4029-9647-FB5F8B67C553} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} - {11ABE2F8-4FB9-48AC-91AA-D04503059550} = {1A2F9F4A-A032-433E-B914-ADD5992BB178} {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD} {9C524CA0-92FF-437B-B568-BCE8A794A69A} = {2E666815-2EDB-464B-9DF6-380BF4789AD4} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {895BDE3D-0E51-485D-908E-16FB1A74FA64} + EndGlobalSection EndGlobal diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs b/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs index e69de29bb2d1..6eccbcfdfff4 100644 --- a/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs +++ b/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs @@ -0,0 +1,97 @@ +// 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. +// ------------------------------------------------------------------------------ +// Changes to this file must follow the http://aka.ms/api-review process. +// ------------------------------------------------------------------------------ + +namespace System.IO.Pipelines +{ + public partial struct FlushResult + { + private int _dummy; + public FlushResult(bool isCanceled, bool isCompleted) { throw null; } + public bool IsCanceled { get { throw null; } } + public bool IsCompleted { get { throw null; } } + } + public partial interface IDuplexPipe : System.IDisposable + { + System.IO.Pipelines.PipeReader Input { get; } + System.IO.Pipelines.PipeWriter Output { get; } + } + public partial interface IPipeAwaiter + { + bool IsCompleted { get; } + T GetResult(); + void OnCompleted(System.Action continuation); + } + public sealed partial class Pipe + { + public Pipe() { } + public Pipe(System.IO.Pipelines.PipeOptions options) { } + public System.IO.Pipelines.PipeReader Reader { get { throw null; } } + public System.IO.Pipelines.PipeWriter Writer { get { throw null; } } + public void Reset() { } + } + public partial struct PipeAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion, System.Runtime.CompilerServices.INotifyCompletion + { + private object _dummy; + public PipeAwaiter(System.IO.Pipelines.IPipeAwaiter awaiter) { throw null; } + public bool IsCompleted { get { throw null; } } + public System.IO.Pipelines.PipeAwaiter GetAwaiter() { throw null; } + public T GetResult() { throw null; } + public void OnCompleted(System.Action continuation) { } + public void UnsafeOnCompleted(System.Action continuation) { } + } + public partial class PipeOptions + { + public PipeOptions(System.Buffers.MemoryPool pool = null, System.IO.Pipelines.PipeScheduler readerScheduler = null, System.IO.Pipelines.PipeScheduler writerScheduler = null, long pauseWriterThreshold = (long)0, long resumeWriterThreshold = (long)0, int minimumSegmentSize = 2048) { } + public static System.IO.Pipelines.PipeOptions Default { get { throw null; } } + public int MinimumSegmentSize { get { throw null; } } + public long PauseWriterThreshold { get { throw null; } } + public System.Buffers.MemoryPool Pool { get { throw null; } } + public System.IO.Pipelines.PipeScheduler ReaderScheduler { get { throw null; } } + public long ResumeWriterThreshold { get { throw null; } } + public System.IO.Pipelines.PipeScheduler WriterScheduler { get { throw null; } } + } + public abstract partial class PipeReader + { + protected PipeReader() { } + public abstract void AdvanceTo(System.SequencePosition consumed); + public abstract void AdvanceTo(System.SequencePosition consumed, System.SequencePosition examined); + public abstract void CancelPendingRead(); + public abstract void Complete(System.Exception exception = null); + public abstract void OnWriterCompleted(System.Action callback, object state); + public abstract System.IO.Pipelines.PipeAwaiter ReadAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract bool TryRead(out System.IO.Pipelines.ReadResult result); + } + public abstract partial class PipeScheduler + { + protected PipeScheduler() { } + public static System.IO.Pipelines.PipeScheduler Inline { get { throw null; } } + public static System.IO.Pipelines.PipeScheduler ThreadPool { get { throw null; } } + public abstract void Schedule(System.Action action); + public abstract void Schedule(System.Action action, object state); + } + public abstract partial class PipeWriter : System.Buffers.IBufferWriter + { + protected PipeWriter() { } + public abstract void Advance(int bytes); + public abstract void CancelPendingFlush(); + public abstract void Commit(); + public abstract void Complete(System.Exception exception = null); + public abstract System.IO.Pipelines.PipeAwaiter FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public abstract System.Memory GetMemory(int minimumLength = 0); + public abstract System.Span GetSpan(int minimumLength = 0); + public abstract void OnReaderCompleted(System.Action callback, object state); + public virtual System.IO.Pipelines.PipeAwaiter WriteAsync(System.ReadOnlyMemory source) { throw null; } + } + public partial struct ReadResult + { + private object _dummy; + public ReadResult(System.Buffers.ReadOnlySequence buffer, bool isCanceled, bool isCompleted) { throw null; } + public System.Buffers.ReadOnlySequence Buffer { get { throw null; } } + public bool IsCanceled { get { throw null; } } + public bool IsCompleted { get { throw null; } } + } +} diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj index 4be0d4695d9d..b3541effa43f 100644 --- a/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj +++ b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj @@ -10,11 +10,14 @@ + + + + - diff --git a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj index fe2dd574bc6c..214ef10dc2be 100644 --- a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj +++ b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj @@ -32,16 +32,17 @@ + + - \ No newline at end of file diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs index 622e76b5d57b..d0120a74d75c 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs @@ -11,7 +11,7 @@ namespace System.IO.Pipelines /// /// Default and implementation. /// - public sealed class Pipe : IPipeAwaiter, IPipeAwaiter + public sealed class Pipe { private const int SegmentPoolSize = 16; @@ -61,6 +61,9 @@ public sealed class Pipe : IPipeAwaiter, IPipeAwaiter private bool _disposed; + private DefaultPipeReader _reader; + private DefaultPipeWriter _writer; + internal long Length => _length; /// @@ -97,6 +100,10 @@ public Pipe(PipeOptions options) _bufferSegmentPool = new BufferSegment[SegmentPoolSize]; + _readingState = default; + _readerCompletion = default; + _writerCompletion = default; + _pool = options.Pool; _minimumSegmentSize = options.MinimumSegmentSize; _maximumSizeHigh = options.PauseWriterThreshold; @@ -105,8 +112,8 @@ public Pipe(PipeOptions options) _writerScheduler = options.WriterScheduler ?? PipeScheduler.Inline; _readerAwaitable = new PipeAwaitable(completed: false); _writerAwaitable = new PipeAwaitable(completed: true); - Reader = new DefaultPipeReader(this); - Writer = new DefaultPipeWriter(this); + _reader = new DefaultPipeReader(this); + _writer = new DefaultPipeWriter(this); } private void ResetState() @@ -315,7 +322,7 @@ internal PipeAwaiter FlushAsync(CancellationToken cancellationToken TrySchedule(_readerScheduler, awaitable); - return new PipeAwaiter(this); + return new PipeAwaiter(_writer); } internal void CompleteWriter(Exception exception) @@ -490,9 +497,6 @@ internal void OnWriterCompleted(Action callback, object state } } - /// - /// Cancel to currently pending call to without completing the . - /// internal void CancelPendingRead() { Action awaitable; @@ -503,9 +507,6 @@ internal void CancelPendingRead() TrySchedule(_readerScheduler, awaitable); } - /// - /// Cancel to currently pending call to without completing the . - /// internal void CancelPendingFlush() { Action awaitable; @@ -547,7 +548,7 @@ internal PipeAwaiter ReadAsync(CancellationToken token) cancellationTokenRegistration = _readerAwaitable.AttachToken(token, _signalReaderAwaitable, this); } cancellationTokenRegistration.Dispose(); - return new PipeAwaiter(this); + return new PipeAwaiter(_reader); } internal bool TryRead(out ReadResult result) @@ -618,9 +619,9 @@ private void CompletePipe() } } - bool IPipeAwaiter.IsCompleted => _readerAwaitable.IsCompleted; + internal bool IsReadAsyncCompleted => _readerAwaitable.IsCompleted; - void IPipeAwaiter.OnCompleted(Action continuation) + internal void OnReadAsyncCompleted(Action continuation) { Action awaitable; bool doubleCompletion; @@ -635,7 +636,7 @@ void IPipeAwaiter.OnCompleted(Action continuation) TrySchedule(_readerScheduler, awaitable); } - ReadResult IPipeAwaiter.GetResult() + internal ReadResult GetReadAsyncResult() { if (!_readerAwaitable.IsCompleted) { @@ -682,9 +683,9 @@ private void GetResult(ref ReadResult result) } } - bool IPipeAwaiter.IsCompleted => _writerAwaitable.IsCompleted; + internal bool IsFlushAsyncCompleted => _writerAwaitable.IsCompleted; - FlushResult IPipeAwaiter.GetResult() + internal FlushResult GetFlushAsyncResult() { var result = new FlushResult(); lock (_sync) @@ -708,7 +709,7 @@ FlushResult IPipeAwaiter.GetResult() return result; } - void IPipeAwaiter.OnCompleted(Action continuation) + internal void OnFlushAsyncCompleted(Action continuation) { Action awaitable; bool doubleCompletion; @@ -743,10 +744,19 @@ private void WriterCancellationRequested() TrySchedule(_writerScheduler, action); } - public PipeReader Reader { get; } + /// + /// Gets the for this pipe. + /// + public PipeReader Reader => _reader; - public PipeWriter Writer { get; } + /// + /// Gets the for this pipe. + /// + public PipeWriter Writer => _writer; + /// + /// Resets the pipe + /// public void Reset() { lock (_sync) @@ -761,7 +771,7 @@ public void Reset() } } - private sealed class DefaultPipeReader : PipeReader + private sealed class DefaultPipeReader : PipeReader, IPipeAwaiter { private readonly Pipe _pipe; @@ -804,9 +814,15 @@ public override void OnWriterCompleted(Action callback, objec { _pipe.OnWriterCompleted(callback, state); } + + public bool IsCompleted => _pipe.IsReadAsyncCompleted; + + public ReadResult GetResult() => _pipe.GetReadAsyncResult(); + + public void OnCompleted(Action continuation) => _pipe.OnReadAsyncCompleted(continuation); } - private sealed class DefaultPipeWriter : PipeWriter + private sealed class DefaultPipeWriter : PipeWriter, IPipeAwaiter { private readonly Pipe _pipe; @@ -854,6 +870,12 @@ public override Span GetSpan(int minimumLength = 0) { return _pipe.GetSpan(minimumLength); } + + public bool IsCompleted => _pipe.IsFlushAsyncCompleted; + + public FlushResult GetResult() => _pipe.GetFlushAsyncResult(); + + public void OnCompleted(Action continuation) => _pipe.OnFlushAsyncCompleted(continuation); } } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaiter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaiter.cs index 2c96a97ec9d5..ba1942e981a5 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaiter.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaiter.cs @@ -13,6 +13,9 @@ public struct PipeAwaiter : ICriticalNotifyCompletion { private readonly IPipeAwaiter _awaiter; + /// + /// Create new instance of that wraps async operation implemented by + /// public PipeAwaiter(IPipeAwaiter awaiter) { _awaiter = awaiter; diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReader.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReader.cs index a66eaf793819..dbef40327631 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReader.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReader.cs @@ -16,7 +16,7 @@ public abstract class PipeReader /// /// The /// True if data was available, or if the call was canceled or the writer was completed. - /// If the pipe returns false, there's no need to call . + /// If the pipe returns false, there's no need to call . public abstract bool TryRead(out ReadResult result); /// diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs index 889e33d4e7b7..353945f486ed 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs @@ -23,12 +23,12 @@ public abstract class PipeScheduler public static PipeScheduler Inline => _inlineScheduler; /// - /// Requests to be run on scheduler + /// Requests to be run on scheduler /// public abstract void Schedule(Action action); /// - /// Requests to be run on scheduler with being passed in + /// Requests to be run on scheduler with being passed in /// public abstract void Schedule(Action action, object state); } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs index a04140d010eb..f4709adef574 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs @@ -51,7 +51,38 @@ public abstract class PipeWriter : IBufferWriter /// public virtual PipeAwaiter WriteAsync(ReadOnlyMemory source) { - this.Write(source.Span); + Span destination = GetSpan(); + var sourceSpan = source.Span; + // Fast path, try copying to the available memory directly + if (sourceSpan.Length <= destination.Length) + { + sourceSpan.CopyTo(destination); + Advance(sourceSpan.Length); + return FlushAsync(); + } + + var remaining = sourceSpan.Length; + var offset = 0; + + while (remaining > 0) + { + var writable = Math.Min(remaining, destination.Length); + + destination = GetSpan(writable); + + if (writable == 0) + { + continue; + } + + sourceSpan.Slice(offset, writable).CopyTo(destination); + + remaining -= writable; + offset += writable; + + Advance(writable); + } + return FlushAsync(); } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs index d45a9e4889e5..c935c9b94524 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs @@ -32,7 +32,7 @@ public ReadResult(ReadOnlySequence buffer, bool isCanceled, bool isComplet } /// - /// The that was read + /// The that was read /// public ReadOnlySequence Buffer => ResultBuffer; diff --git a/src/System.IO.Pipelines/tests/BackpressureTests.cs b/src/System.IO.Pipelines/tests/BackpressureTests.cs new file mode 100644 index 000000000000..b0acf91fe5b3 --- /dev/null +++ b/src/System.IO.Pipelines/tests/BackpressureTests.cs @@ -0,0 +1,143 @@ +// 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.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class BackpressureTests : IDisposable + { + public BackpressureTests() + { + _pool = new TestMemoryPool(); + _pipe = new Pipe(new PipeOptions(_pool, resumeWriterThreshold: 32, pauseWriterThreshold: 64)); + } + + public void Dispose() + { + _pipe.Writer.Complete(); + _pipe.Reader.Complete(); + _pool?.Dispose(); + } + + private readonly TestMemoryPool _pool; + + private readonly Pipe _pipe; + + [Fact] + public void AdvanceThrowsIfFlushActiveAndNotConsumedPastThreshold() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(64); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + Assert.False(flushAsync.IsCompleted); + + ReadResult result = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); + SequencePosition consumed = result.Buffer.GetPosition(result.Buffer.Start, 31); + Assert.Throws(() => _pipe.Reader.AdvanceTo(consumed, result.Buffer.End)); + + _pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); + } + + [Fact] + public void FlushAsyncAwaitableCompletesWhenReaderAdvancesUnderLow() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(64); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + + Assert.False(flushAsync.IsCompleted); + + ReadResult result = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); + SequencePosition consumed = result.Buffer.GetPosition(result.Buffer.Start, 33); + _pipe.Reader.AdvanceTo(consumed, consumed); + + Assert.True(flushAsync.IsCompleted); + FlushResult flushResult = flushAsync.GetResult(); + Assert.False(flushResult.IsCompleted); + } + + [Fact] + public void FlushAsyncAwaitableDoesNotCompletesWhenReaderAdvancesUnderHight() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(64); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + + Assert.False(flushAsync.IsCompleted); + + ReadResult result = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); + SequencePosition consumed = result.Buffer.GetPosition(result.Buffer.Start, 32); + _pipe.Reader.AdvanceTo(consumed, consumed); + + Assert.False(flushAsync.IsCompleted); + } + + [Fact] + public void FlushAsyncAwaitableResetsOnCommit() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(64); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + + Assert.False(flushAsync.IsCompleted); + + ReadResult result = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); + SequencePosition consumed = result.Buffer.GetPosition(result.Buffer.Start, 33); + _pipe.Reader.AdvanceTo(consumed, consumed); + + Assert.True(flushAsync.IsCompleted); + FlushResult flushResult = flushAsync.GetResult(); + Assert.False(flushResult.IsCompleted); + + writableBuffer = _pipe.Writer.WriteEmpty(64); + flushAsync = writableBuffer.FlushAsync(); + + Assert.False(flushAsync.IsCompleted); + } + + [Fact] + public void FlushAsyncReturnsCompletedIfReaderCompletes() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(64); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + + Assert.False(flushAsync.IsCompleted); + + _pipe.Reader.Complete(); + + Assert.True(flushAsync.IsCompleted); + FlushResult flushResult = flushAsync.GetResult(); + Assert.True(flushResult.IsCompleted); + } + + [Fact] + public void FlushAsyncReturnsCompletedTaskWhenSizeLessThenLimit() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(32); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + Assert.True(flushAsync.IsCompleted); + FlushResult flushResult = flushAsync.GetResult(); + Assert.False(flushResult.IsCompleted); + } + + [Fact] + public void FlushAsyncReturnsNonCompletedSizeWhenCommitOverTheLimit() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(64); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + Assert.False(flushAsync.IsCompleted); + } + + [Fact] + public async Task FlushAsyncThrowsIfReaderCompletedWithException() + { + _pipe.Reader.Complete(new InvalidOperationException("Reader failed")); + + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(64); + InvalidOperationException invalidOperationException = + await Assert.ThrowsAsync(async () => await writableBuffer.FlushAsync()); + Assert.Equal("Reader failed", invalidOperationException.Message); + invalidOperationException = await Assert.ThrowsAsync(async () => await writableBuffer.FlushAsync()); + Assert.Equal("Reader failed", invalidOperationException.Message); + } + } +} diff --git a/src/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs b/src/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs new file mode 100644 index 000000000000..fe3302ccef5d --- /dev/null +++ b/src/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs @@ -0,0 +1,320 @@ +// 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.Buffers; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class FlushAsyncCancellationTests : PipeTest + { + [Fact] + public void FlushAsyncCancellationDeadlock() + { + var cts = new CancellationTokenSource(); + var cts2 = new CancellationTokenSource(); + + PipeWriter buffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + + var e = new ManualResetEventSlim(); + + PipeAwaiter awaiter = buffer.FlushAsync(cts.Token); + awaiter.OnCompleted( + () => { + // We are on cancellation thread and need to wait untill another FlushAsync call + // takes pipe state lock + e.Wait(); + + // Make sure we had enough time to reach _cancellationTokenRegistration.Dispose + Thread.Sleep(100); + + // Try to take pipe state lock + buffer.FlushAsync(); + }); + + // Start a thread that would run cancellation calbacks + Task cancellationTask = Task.Run(() => cts.Cancel()); + // Start a thread that would call FlushAsync with different token + // and block on _cancellationTokenRegistration.Dispose + Task blockingTask = Task.Run( + () => { + e.Set(); + buffer.FlushAsync(cts2.Token); + }); + + bool completed = Task.WhenAll(cancellationTask, blockingTask).Wait(TimeSpan.FromSeconds(10)); + Assert.True(completed); + } + + [Fact] + public async Task FlushAsyncCancellationE2E() + { + var cts = new CancellationTokenSource(); + var cancelled = false; + + Func taskFunc = async () => { + try + { + Pipe.Writer.WriteEmpty(MaximumSizeHigh); + await Pipe.Writer.FlushAsync(cts.Token); + } + catch (OperationCanceledException) + { + cancelled = true; + await Pipe.Writer.FlushAsync(); + } + }; + + Task task = taskFunc(); + + cts.Cancel(); + + ReadResult result = await Pipe.Reader.ReadAsync(); + Assert.Equal(new byte[MaximumSizeHigh], result.Buffer.ToArray()); + Pipe.Reader.AdvanceTo(result.Buffer.End); + await task; + Assert.True(cancelled); + } + + [Fact] + public void FlushAsyncCompletedAfterPreCancellation() + { + PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(1); + + Pipe.Writer.CancelPendingFlush(); + + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + + Assert.True(flushAsync.IsCompleted); + + FlushResult flushResult = flushAsync.GetResult(); + + Assert.True(flushResult.IsCanceled); + + flushAsync = writableBuffer.FlushAsync(); + + Assert.True(flushAsync.IsCompleted); + } + + [Fact] + public void FlushAsyncNotCompletedAfterCancellation() + { + var onCompletedCalled = false; + PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + + PipeAwaiter awaitable = writableBuffer.FlushAsync(); + + Assert.False(awaitable.IsCompleted); + awaitable.OnCompleted( + () => { + onCompletedCalled = true; + Assert.True(awaitable.IsCompleted); + + FlushResult flushResult = awaitable.GetResult(); + + Assert.True(flushResult.IsCanceled); + + awaitable = writableBuffer.FlushAsync(); + Assert.False(awaitable.IsCompleted); + }); + + Pipe.Writer.CancelPendingFlush(); + Assert.True(onCompletedCalled); + } + + [Fact] + public void FlushAsyncNotCompletedAfterCancellationTokenCancelled() + { + var onCompletedCalled = false; + var cts = new CancellationTokenSource(); + PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + + PipeAwaiter awaitable = writableBuffer.FlushAsync(cts.Token); + + Assert.False(awaitable.IsCompleted); + awaitable.OnCompleted( + () => { + onCompletedCalled = true; + Assert.True(awaitable.IsCompleted); + + Assert.Throws(() => awaitable.GetResult()); + + awaitable = writableBuffer.FlushAsync(); + Assert.False(awaitable.IsCompleted); + }); + + cts.Cancel(); + Assert.True(onCompletedCalled); + } + + [Fact] + public void FlushAsyncReturnsCanceledIfCancelledBeforeFlush() + { + PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + + Pipe.Writer.CancelPendingFlush(); + + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + + Assert.True(flushAsync.IsCompleted); + FlushResult flushResult = flushAsync.GetResult(); + Assert.True(flushResult.IsCanceled); + } + + [Fact] + public void FlushAsyncReturnsCanceledIfFlushCancelled() + { + PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + + Assert.False(flushAsync.IsCompleted); + + Pipe.Writer.CancelPendingFlush(); + + Assert.True(flushAsync.IsCompleted); + FlushResult flushResult = flushAsync.GetResult(); + Assert.True(flushResult.IsCanceled); + } + + [Fact] + public void FlushAsyncReturnsIsCancelOnCancelPendingFlushAfterGetResult() + { + PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + PipeAwaiter awaitable = writableBuffer.FlushAsync(); + + Assert.False(awaitable.IsCompleted); + awaitable.OnCompleted(() => { }); + + Pipe.Writer.CancelPendingFlush(); + Pipe.Reader.AdvanceTo(Pipe.Reader.ReadAsync().GetResult().Buffer.End); + + Assert.True(awaitable.IsCompleted); + + FlushResult result = awaitable.GetResult(); + Assert.True(result.IsCanceled); + } + + [Fact] + public void FlushAsyncReturnsIsCancelOnCancelPendingFlushBeforeGetResult() + { + PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + PipeAwaiter awaitable = writableBuffer.FlushAsync(); + + Assert.False(awaitable.IsCompleted); + awaitable.OnCompleted(() => { }); + + Pipe.Reader.AdvanceTo(Pipe.Reader.ReadAsync().GetResult().Buffer.End); + Pipe.Writer.CancelPendingFlush(); + + Assert.True(awaitable.IsCompleted); + + FlushResult result = awaitable.GetResult(); + Assert.True(result.IsCanceled); + } + + [Fact] + public void FlushAsyncThrowsIfPassedCancelledCancellationToken() + { + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + + Assert.Throws(() => Pipe.Writer.FlushAsync(cancellationTokenSource.Token)); + } + + [Fact] + public async Task FlushAsyncWithNewCancellationTokenNotAffectedByPrevious() + { + var cancellationTokenSource1 = new CancellationTokenSource(); + PipeWriter buffer = Pipe.Writer.WriteEmpty(10); + await buffer.FlushAsync(cancellationTokenSource1.Token); + + cancellationTokenSource1.Cancel(); + + var cancellationTokenSource2 = new CancellationTokenSource(); + buffer = Pipe.Writer.WriteEmpty(10); + // Verifying that ReadAsync does not throw + await buffer.FlushAsync(cancellationTokenSource2.Token); + } + + [Fact] + public void GetResultThrowsIfFlushAsyncCancelledAfterOnCompleted() + { + var onCompletedCalled = false; + var cancellationTokenSource = new CancellationTokenSource(); + PipeWriter buffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + + PipeAwaiter awaiter = buffer.FlushAsync(cancellationTokenSource.Token); + + awaiter.OnCompleted( + () => { + onCompletedCalled = true; + Assert.Throws(() => awaiter.GetResult()); + }); + + bool awaiterIsCompleted = awaiter.IsCompleted; + + cancellationTokenSource.Cancel(); + + Assert.False(awaiterIsCompleted); + Assert.True(onCompletedCalled); + } + + [Fact] + public void GetResultThrowsIfFlushAsyncCancelledBeforeOnCompleted() + { + var onCompletedCalled = false; + var cancellationTokenSource = new CancellationTokenSource(); + PipeWriter buffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + + PipeAwaiter awaiter = buffer.FlushAsync(cancellationTokenSource.Token); + bool awaiterIsCompleted = awaiter.IsCompleted; + + cancellationTokenSource.Cancel(); + + awaiter.OnCompleted( + () => { + onCompletedCalled = true; + Assert.Throws(() => awaiter.GetResult()); + }); + + Assert.False(awaiterIsCompleted); + Assert.True(onCompletedCalled); + } + + [Fact] + public void GetResultThrowsIfFlushAsyncTokenFiredAfterCancelPending() + { + var onCompletedCalled = false; + var cancellationTokenSource = new CancellationTokenSource(); + PipeWriter buffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); + + PipeAwaiter awaiter = buffer.FlushAsync(cancellationTokenSource.Token); + bool awaiterIsCompleted = awaiter.IsCompleted; + + Pipe.Writer.CancelPendingFlush(); + cancellationTokenSource.Cancel(); + + awaiter.OnCompleted( + () => { + onCompletedCalled = true; + Assert.Throws(() => awaiter.GetResult()); + }); + + Assert.False(awaiterIsCompleted); + Assert.True(onCompletedCalled); + } + } + + public static class TestWriterExtensions + { + public static PipeWriter WriteEmpty(this PipeWriter writer, int count) + { + writer.GetMemory(count); + writer.Advance(count); + return writer; + } + } +} diff --git a/src/System.IO.Pipelines/tests/FlushAsyncCompletionTests.cs b/src/System.IO.Pipelines/tests/FlushAsyncCompletionTests.cs new file mode 100644 index 000000000000..6a28ad238082 --- /dev/null +++ b/src/System.IO.Pipelines/tests/FlushAsyncCompletionTests.cs @@ -0,0 +1,37 @@ +// 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.Buffers; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class FlushAsyncCompletionTests : PipeTest + { + [Fact] + public void AwaitingFlushAsyncAwaitableTwiceCompletesReaderWithException() + { + async Task Await(PipeAwaiter a) + { + await a; + } + + PipeWriter writeBuffer = Pipe.Writer; + writeBuffer.Write(new byte[MaximumSizeHigh]); + PipeAwaiter awaitable = writeBuffer.FlushAsync(); + + Task task1 = Await(awaitable); + Task task2 = Await(awaitable); + + Assert.Equal(true, task1.IsCompleted); + Assert.Equal(true, task1.IsFaulted); + Assert.Equal("Concurrent reads or writes are not supported.", task1.Exception.InnerExceptions[0].Message); + + Assert.Equal(true, task2.IsCompleted); + Assert.Equal(true, task2.IsFaulted); + Assert.Equal("Concurrent reads or writes are not supported.", task2.Exception.InnerExceptions[0].Message); + } + } +} diff --git a/src/System.IO.Pipelines/tests/FlushResultTests.cs b/src/System.IO.Pipelines/tests/FlushResultTests.cs new file mode 100644 index 000000000000..50ac67edcbb0 --- /dev/null +++ b/src/System.IO.Pipelines/tests/FlushResultTests.cs @@ -0,0 +1,24 @@ +// 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 Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class FlushResultTests + { + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + [Theory] + public void FlushResultCanBeConstructed(bool cancelled, bool completed) + { + var result = new FlushResult(cancelled, completed); + + Assert.Equal(cancelled, result.IsCanceled); + Assert.Equal(completed, result.IsCompleted); + } + } +} diff --git a/src/System.IO.Pipelines/tests/PipeCompletionCallbacksTests.cs b/src/System.IO.Pipelines/tests/PipeCompletionCallbacksTests.cs new file mode 100644 index 000000000000..a9ab3109aa0d --- /dev/null +++ b/src/System.IO.Pipelines/tests/PipeCompletionCallbacksTests.cs @@ -0,0 +1,472 @@ +// 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 Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class PipeCompletionCallbacksTests : IDisposable + { + public PipeCompletionCallbacksTests() + { + _pool = new TestMemoryPool(); + } + + public void Dispose() + { + _pool.Dispose(); + } + + private readonly TestMemoryPool _pool; + + private class TestScheduler : PipeScheduler + { + public int CallCount { get; set; } + + public Exception LastException { get; set; } + + public override void Schedule(Action action) + { + Schedule(o => ((Action)o)(), action); + } + + public override void Schedule(Action action, object state) + { + CallCount++; + try + { + action(state); + } + catch (Exception e) + { + LastException = e; + } + } + } + + [Fact] + public void CompletingReaderFromWriterCallbackWorks() + { + var callbackRan = false; + var pipe = new Pipe(new PipeOptions(_pool, pauseWriterThreshold: 5)); + + pipe.Writer.OnReaderCompleted((exception, state) => { pipe.Writer.Complete(); }, null); + + pipe.Reader.OnWriterCompleted((exception, state) => { callbackRan = true; }, null); + + pipe.Reader.Complete(); + Assert.True(callbackRan); + } + + [Fact] + public void CompletingWriterFromReaderCallbackWorks() + { + var callbackRan = false; + var pipe = new Pipe(new PipeOptions(_pool, pauseWriterThreshold: 5)); + + pipe.Reader.OnWriterCompleted((exception, state) => { pipe.Reader.Complete(); }, null); + + pipe.Writer.OnReaderCompleted((exception, state) => { callbackRan = true; }, null); + + pipe.Writer.Complete(); + Assert.True(callbackRan); + } + + [Fact] + public void OnReaderCompletedContinuesOnException() + { + var callbackState1 = new object(); + var callbackState2 = new object(); + var exception1 = new Exception(); + var exception2 = new Exception(); + + var counter = 0; + + var pipe = new Pipe(new PipeOptions(_pool)); + pipe.Writer.OnReaderCompleted( + (exception, state) => { + Assert.Equal(callbackState1, state); + Assert.Equal(0, counter); + counter++; + throw exception1; + }, callbackState1); + + pipe.Writer.OnReaderCompleted( + (exception, state) => { + Assert.Equal(callbackState2, state); + Assert.Equal(1, counter); + counter++; + throw exception2; + }, callbackState2); + + var aggregateException = Assert.Throws(() => pipe.Reader.Complete()); + Assert.Equal(exception1, aggregateException.InnerExceptions[0]); + Assert.Equal(exception2, aggregateException.InnerExceptions[1]); + Assert.Equal(2, counter); + } + + [Fact] + public void OnReaderCompletedExceptionSurfacesToWriterScheduler() + { + var exception = new Exception(); + var scheduler = new TestScheduler(); + var pipe = new Pipe(new PipeOptions(_pool, writerScheduler: scheduler)); + pipe.Writer.OnReaderCompleted((e, state) => throw exception, null); + pipe.Reader.Complete(); + + Assert.Equal(1, scheduler.CallCount); + var aggregateException = Assert.IsType(scheduler.LastException); + Assert.Equal(aggregateException.InnerExceptions[0], exception); + } + + [Fact] + public void OnReaderCompletedExecutesOnSchedulerIfCompleted() + { + var callbackRan = false; + var scheduler = new TestScheduler(); + var pipe = new Pipe(new PipeOptions(_pool, writerScheduler: scheduler)); + pipe.Reader.Complete(); + + pipe.Writer.OnReaderCompleted( + (exception, state) => { + Assert.Null(exception); + callbackRan = true; + }, null); + + Assert.True(callbackRan); + Assert.Equal(1, scheduler.CallCount); + } + + [Fact] + public void OnReaderCompletedIsDetachedDuringReset() + { + var callbackRan = false; + var scheduler = new TestScheduler(); + var pipe = new Pipe(new PipeOptions(_pool, writerScheduler: scheduler)); + pipe.Writer.OnReaderCompleted((exception, state) => { callbackRan = true; }, null); + + pipe.Reader.Complete(); + pipe.Writer.Complete(); + pipe.Reset(); + + Assert.True(callbackRan); + callbackRan = false; + + pipe.Reader.Complete(); + pipe.Writer.Complete(); + + Assert.Equal(1, scheduler.CallCount); + Assert.False(callbackRan); + } + + [Fact] + public void OnReaderCompletedPassesException() + { + var callbackRan = false; + var pipe = new Pipe(new PipeOptions(_pool)); + var readerException = new Exception(); + + pipe.Writer.OnReaderCompleted( + (exception, state) => { + callbackRan = true; + Assert.Same(readerException, exception); + }, null); + pipe.Reader.Complete(readerException); + + Assert.True(callbackRan); + } + + [Fact] + public void OnReaderCompletedPassesState() + { + var callbackRan = false; + var callbackState = new object(); + var pipe = new Pipe(new PipeOptions(_pool)); + pipe.Writer.OnReaderCompleted( + (exception, state) => { + Assert.Equal(callbackState, state); + callbackRan = true; + }, callbackState); + pipe.Reader.Complete(); + + Assert.True(callbackRan); + } + + [Fact] + public void OnReaderCompletedRanBeforeFlushContinuation() + { + var callbackRan = false; + var continuationRan = false; + var pipe = new Pipe(new PipeOptions(_pool, pauseWriterThreshold: 5)); + + pipe.Writer.OnReaderCompleted( + (exception, state) => { + Assert.False(continuationRan); + callbackRan = true; + }, null); + + PipeWriter buffer = pipe.Writer.WriteEmpty(10); + PipeAwaiter awaiter = buffer.FlushAsync(); + + Assert.False(awaiter.IsCompleted); + awaiter.OnCompleted(() => { continuationRan = true; }); + pipe.Reader.Complete(); + + Assert.True(callbackRan); + pipe.Writer.Complete(); + } + + [Fact] + public void OnReaderCompletedRunsInRegistrationOrder() + { + var callbackState1 = new object(); + var callbackState2 = new object(); + var callbackState3 = new object(); + var counter = 0; + + var pipe = new Pipe(new PipeOptions(_pool)); + pipe.Writer.OnReaderCompleted( + (exception, state) => { + Assert.Equal(callbackState1, state); + Assert.Equal(0, counter); + counter++; + }, callbackState1); + + pipe.Writer.OnReaderCompleted( + (exception, state) => { + Assert.Equal(callbackState2, state); + Assert.Equal(1, counter); + counter++; + }, callbackState2); + + pipe.Writer.OnReaderCompleted( + (exception, state) => { + Assert.Equal(callbackState3, state); + Assert.Equal(2, counter); + counter++; + }, callbackState3); + + pipe.Reader.Complete(); + + Assert.Equal(3, counter); + } + + [Fact] + public void OnReaderCompletedThrowsWithNullCallback() + { + var pipe = new Pipe(new PipeOptions(_pool)); + + Assert.Throws(() => pipe.Writer.OnReaderCompleted(null, null)); + } + + [Fact] + public void OnReaderCompletedUsingWriterScheduler() + { + var callbackRan = false; + var scheduler = new TestScheduler(); + var pipe = new Pipe(new PipeOptions(_pool, writerScheduler: scheduler)); + pipe.Writer.OnReaderCompleted((exception, state) => { callbackRan = true; }, null); + pipe.Reader.Complete(); + + Assert.True(callbackRan); + Assert.Equal(1, scheduler.CallCount); + } + + [Fact] + public void OnWriterCompletedContinuesOnException() + { + var callbackState1 = new object(); + var callbackState2 = new object(); + var exception1 = new Exception(); + var exception2 = new Exception(); + + var counter = 0; + + var pipe = new Pipe(new PipeOptions(_pool)); + pipe.Reader.OnWriterCompleted( + (exception, state) => { + Assert.Equal(callbackState1, state); + Assert.Equal(0, counter); + counter++; + throw exception1; + }, callbackState1); + + pipe.Reader.OnWriterCompleted( + (exception, state) => { + Assert.Equal(callbackState2, state); + Assert.Equal(1, counter); + counter++; + throw exception2; + }, callbackState2); + + var aggregateException = Assert.Throws(() => pipe.Writer.Complete()); + Assert.Equal(exception1, aggregateException.InnerExceptions[0]); + Assert.Equal(exception2, aggregateException.InnerExceptions[1]); + Assert.Equal(2, counter); + } + + [Fact] + public void OnWriterCompletedExceptionSurfacesToReaderScheduler() + { + var exception = new Exception(); + var scheduler = new TestScheduler(); + var pipe = new Pipe(new PipeOptions(_pool, scheduler)); + pipe.Reader.OnWriterCompleted((e, state) => throw exception, null); + pipe.Writer.Complete(); + + Assert.Equal(1, scheduler.CallCount); + var aggregateException = Assert.IsType(scheduler.LastException); + Assert.Equal(aggregateException.InnerExceptions[0], exception); + } + + [Fact] + public void OnWriterCompletedExecutedSchedulerIfCompleted() + { + var callbackRan = false; + var scheduler = new TestScheduler(); + var pipe = new Pipe(new PipeOptions(_pool, scheduler)); + pipe.Writer.Complete(); + + pipe.Reader.OnWriterCompleted( + (exception, state) => { + Assert.Null(exception); + callbackRan = true; + }, null); + + Assert.True(callbackRan); + Assert.Equal(1, scheduler.CallCount); + } + + [Fact] + public void OnWriterCompletedIsDetachedDuringReset() + { + var callbackRan = false; + var scheduler = new TestScheduler(); + var pipe = new Pipe(new PipeOptions(_pool, scheduler)); + pipe.Reader.OnWriterCompleted((exception, state) => { callbackRan = true; }, null); + pipe.Reader.Complete(); + pipe.Writer.Complete(); + pipe.Reset(); + + Assert.True(callbackRan); + callbackRan = false; + + pipe.Reader.Complete(); + pipe.Writer.Complete(); + + Assert.Equal(1, scheduler.CallCount); + Assert.False(callbackRan); + } + + [Fact] + public void OnWriterCompletedPassesException() + { + var callbackRan = false; + var pipe = new Pipe(new PipeOptions(_pool)); + var readerException = new Exception(); + + pipe.Reader.OnWriterCompleted( + (exception, state) => { + callbackRan = true; + Assert.Same(readerException, exception); + }, null); + pipe.Writer.Complete(readerException); + + Assert.True(callbackRan); + } + + [Fact] + public void OnWriterCompletedPassesState() + { + var callbackRan = false; + var callbackState = new object(); + var pipe = new Pipe(new PipeOptions(_pool)); + pipe.Reader.OnWriterCompleted( + (exception, state) => { + Assert.Equal(callbackState, state); + callbackRan = true; + }, callbackState); + pipe.Writer.Complete(); + + Assert.True(callbackRan); + } + + [Fact] + public void OnWriterCompletedRanBeforeReadContinuation() + { + var callbackRan = false; + var continuationRan = false; + var pipe = new Pipe(new PipeOptions(_pool)); + + pipe.Reader.OnWriterCompleted( + (exception, state) => { + callbackRan = true; + Assert.False(continuationRan); + }, null); + + PipeAwaiter awaiter = pipe.Reader.ReadAsync(); + Assert.False(awaiter.IsCompleted); + awaiter.OnCompleted(() => { continuationRan = true; }); + pipe.Writer.Complete(); + + Assert.True(callbackRan); + } + + [Fact] + public void OnWriterCompletedRunsInRegistrationOrder() + { + var callbackState1 = new object(); + var callbackState2 = new object(); + var callbackState3 = new object(); + var counter = 0; + + var pipe = new Pipe(new PipeOptions(_pool)); + pipe.Reader.OnWriterCompleted( + (exception, state) => { + Assert.Equal(callbackState1, state); + Assert.Equal(0, counter); + counter++; + }, callbackState1); + + pipe.Reader.OnWriterCompleted( + (exception, state) => { + Assert.Equal(callbackState2, state); + Assert.Equal(1, counter); + counter++; + }, callbackState2); + + pipe.Reader.OnWriterCompleted( + (exception, state) => { + Assert.Equal(callbackState3, state); + Assert.Equal(2, counter); + counter++; + }, callbackState3); + + pipe.Writer.Complete(); + + Assert.Equal(3, counter); + } + + [Fact] + public void OnWriterCompletedThrowsWithNullCallback() + { + var pipe = new Pipe(new PipeOptions(_pool)); + + Assert.Throws(() => pipe.Reader.OnWriterCompleted(null, null)); + } + + [Fact] + public void OnWriterCompletedUsingReaderScheduler() + { + var callbackRan = false; + var scheduler = new TestScheduler(); + var pipe = new Pipe(new PipeOptions(_pool, scheduler)); + pipe.Reader.OnWriterCompleted((exception, state) => { callbackRan = true; }, null); + pipe.Writer.Complete(); + + Assert.True(callbackRan); + Assert.Equal(1, scheduler.CallCount); + } + } +} diff --git a/src/System.IO.Pipelines/tests/PipeLengthTests.cs b/src/System.IO.Pipelines/tests/PipeLengthTests.cs new file mode 100644 index 000000000000..fe413dae817e --- /dev/null +++ b/src/System.IO.Pipelines/tests/PipeLengthTests.cs @@ -0,0 +1,102 @@ +// 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 Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class PipeLengthTests : IDisposable + { + public PipeLengthTests() + { + _pool = new TestMemoryPool(); + _pipe = new Pipe(new PipeOptions(_pool)); + } + + public void Dispose() + { + _pipe.Writer.Complete(); + _pipe.Reader.Complete(); + _pool?.Dispose(); + } + + private readonly TestMemoryPool _pool; + + private readonly Pipe _pipe; + + [Fact] + public void ByteByByteTest() + { + for (var i = 1; i <= 1024 * 1024; i++) + { + _pipe.Writer.GetMemory(100); + _pipe.Writer.Advance(1); + _pipe.Writer.Commit(); + + Assert.Equal(i, _pipe.Length); + } + + _pipe.Writer.FlushAsync(); + + for (int i = 1024 * 1024 - 1; i >= 0; i--) + { + ReadResult result = _pipe.Reader.ReadAsync().GetResult(); + SequencePosition consumed = result.Buffer.Slice(1).Start; + + Assert.Equal(i + 1, result.Buffer.Length); + + _pipe.Reader.AdvanceTo(consumed, consumed); + + Assert.Equal(i, _pipe.Length); + } + } + + [Fact] + public void LengthCorrectAfterAlloc0AdvanceCommit() + { + _pipe.Writer.GetMemory(0); + _pipe.Writer.WriteEmpty(10); + _pipe.Writer.Commit(); + + Assert.Equal(10, _pipe.Length); + } + + [Fact] + public void LengthCorrectAfterAllocAdvanceCommit() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); + writableBuffer.Commit(); + + Assert.Equal(10, _pipe.Length); + } + + [Fact] + public void LengthDecreasedAfterReadAdvanceConsume() + { + _pipe.Writer.GetMemory(100); + _pipe.Writer.Advance(10); + _pipe.Writer.Commit(); + _pipe.Writer.FlushAsync(); + + ReadResult result = _pipe.Reader.ReadAsync().GetResult(); + SequencePosition consumed = result.Buffer.Slice(5).Start; + _pipe.Reader.AdvanceTo(consumed, consumed); + + Assert.Equal(5, _pipe.Length); + } + + [Fact] + public void LengthNotChangeAfterReadAdvanceExamine() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); + writableBuffer.Commit(); + writableBuffer.FlushAsync(); + + ReadResult result = _pipe.Reader.ReadAsync().GetResult(); + _pipe.Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End); + + Assert.Equal(10, _pipe.Length); + } + } +} diff --git a/src/System.IO.Pipelines/tests/PipePoolTests.cs b/src/System.IO.Pipelines/tests/PipePoolTests.cs new file mode 100644 index 000000000000..3a9d2b17cde8 --- /dev/null +++ b/src/System.IO.Pipelines/tests/PipePoolTests.cs @@ -0,0 +1,216 @@ +// 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.Buffers; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class PipePoolTests + { + private class DisposeTrackingBufferPool : TestMemoryPool + { + public int ReturnedBlocks { get; set; } + public int CurrentlyRentedBlocks { get; set; } + + public override OwnedMemory Rent(int size) + { + return new DisposeTrackingOwnedMemory(new byte[size], this); + } + + protected override void Dispose(bool disposing) + { + } + + private class DisposeTrackingOwnedMemory : OwnedMemory + { + private readonly byte[] _array; + + private readonly DisposeTrackingBufferPool _bufferPool; + + public DisposeTrackingOwnedMemory(byte[] array, DisposeTrackingBufferPool bufferPool) + { + _array = array; + _bufferPool = bufferPool; + _bufferPool.CurrentlyRentedBlocks++; + } + + public override int Length => _array.Length; + + public override Span Span + { + get + { + if (IsDisposed) + throw new ObjectDisposedException(nameof(DisposeTrackingBufferPool)); + return _array; + } + } + + public override bool IsDisposed { get; } + + protected override bool IsRetained => true; + + public override MemoryHandle Pin(int byteOffset = 0) + { + throw new NotImplementedException(); + } + + protected override bool TryGetArray(out ArraySegment arraySegment) + { + if (IsDisposed) + throw new ObjectDisposedException(nameof(DisposeTrackingBufferPool)); + arraySegment = new ArraySegment(_array); + return true; + } + + protected override void Dispose(bool disposing) + { + throw new NotImplementedException(); + } + + public override bool Release() + { + _bufferPool.ReturnedBlocks++; + _bufferPool.CurrentlyRentedBlocks--; + return IsRetained; + } + + public override void Retain() + { + } + } + } + + [Fact] + public async Task AdvanceToEndReturnsAllBlocks() + { + var pool = new DisposeTrackingBufferPool(); + + var writeSize = 512; + + var pipe = new Pipe(new PipeOptions(pool)); + while (pool.CurrentlyRentedBlocks != 3) + { + PipeWriter writableBuffer = pipe.Writer.WriteEmpty(writeSize); + await writableBuffer.FlushAsync(); + } + + ReadResult readResult = await pipe.Reader.ReadAsync(); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + Assert.Equal(0, pool.CurrentlyRentedBlocks); + } + + [Fact] + public async Task CanWriteAfterReturningMultipleBlocks() + { + var pool = new DisposeTrackingBufferPool(); + + var writeSize = 512; + + var pipe = new Pipe(new PipeOptions(pool)); + + // Write two blocks + Memory buffer = pipe.Writer.GetMemory(writeSize); + pipe.Writer.Advance(buffer.Length); + pipe.Writer.GetMemory(buffer.Length); + pipe.Writer.Advance(writeSize); + await pipe.Writer.FlushAsync(); + + Assert.Equal(2, pool.CurrentlyRentedBlocks); + + // Read everything + ReadResult readResult = await pipe.Reader.ReadAsync(); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + // Try writing more + await pipe.Writer.WriteAsync(new byte[writeSize]); + } + + [Fact] + public async Task MultipleCompleteReaderWriterCauseDisposeOnlyOnce() + { + var pool = new DisposeTrackingBufferPool(); + + var readerWriter = new Pipe(new PipeOptions(pool)); + await readerWriter.Writer.WriteAsync(new byte[] { 1 }); + + readerWriter.Writer.Complete(); + readerWriter.Reader.Complete(); + Assert.Equal(1, pool.ReturnedBlocks); + + readerWriter.Writer.Complete(); + readerWriter.Reader.Complete(); + Assert.Equal(1, pool.ReturnedBlocks); + } + + [Fact] + public async Task RentsMinimumSegmentSize() + { + var pool = new DisposeTrackingBufferPool(); + var writeSize = 512; + + var pipe = new Pipe(new PipeOptions(pool, minimumSegmentSize: 2020)); + + Memory buffer = pipe.Writer.GetMemory(writeSize); + int allocatedSize = buffer.Length; + pipe.Writer.Advance(buffer.Length); + buffer = pipe.Writer.GetMemory(1); + int ensuredSize = buffer.Length; + await pipe.Writer.FlushAsync(); + + pipe.Reader.Complete(); + pipe.Writer.Complete(); + + Assert.Equal(2020, ensuredSize); + Assert.Equal(2020, allocatedSize); + } + + [Fact] + public void ReturnsWriteHeadOnComplete() + { + var pool = new DisposeTrackingBufferPool(); + var pipe = new Pipe(new PipeOptions(pool)); + var memory = pipe.Writer.GetMemory(512); + + pipe.Reader.Complete(); + pipe.Writer.Complete(); + Assert.Equal(0, pool.CurrentlyRentedBlocks); + } + + [Fact] + public void ReturnsWriteHeadWhenRequestingLargerBlock() + { + var pool = new DisposeTrackingBufferPool(); + var pipe = new Pipe(new PipeOptions(pool)); + var memory = pipe.Writer.GetMemory(512); + pipe.Writer.GetMemory(4096); + + pipe.Reader.Complete(); + pipe.Writer.Complete(); + Assert.Equal(0, pool.CurrentlyRentedBlocks); + } + + [Fact] + public async Task WriteDuringReadIsNotReturned() + { + var pool = new DisposeTrackingBufferPool(); + + var writeSize = 512; + + var pipe = new Pipe(new PipeOptions(pool)); + await pipe.Writer.WriteAsync(new byte[writeSize]); + + pipe.Writer.GetMemory(writeSize); + ReadResult readResult = await pipe.Reader.ReadAsync(); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + pipe.Writer.Write(new byte[writeSize]); + pipe.Writer.Commit(); + + Assert.Equal(1, pool.CurrentlyRentedBlocks); + } + } +} diff --git a/src/System.IO.Pipelines/tests/PipeResetTests.cs b/src/System.IO.Pipelines/tests/PipeResetTests.cs new file mode 100644 index 000000000000..d0582855e7df --- /dev/null +++ b/src/System.IO.Pipelines/tests/PipeResetTests.cs @@ -0,0 +1,81 @@ +// 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.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class PipeResetTests : IDisposable + { + public PipeResetTests() + { + _pool = new TestMemoryPool(); + _pipe = new Pipe(new PipeOptions(_pool)); + } + + public void Dispose() + { + _pipe.Writer.Complete(); + _pipe.Reader.Complete(); + _pool?.Dispose(); + } + + private readonly TestMemoryPool _pool; + + private readonly Pipe _pipe; + + [Fact] + public async Task LengthIsReseted() + { + var source = new byte[] { 1, 2, 3 }; + + await _pipe.Writer.WriteAsync(source); + + _pipe.Reader.Complete(); + _pipe.Writer.Complete(); + + _pipe.Reset(); + + Assert.Equal(0, _pipe.Length); + } + + [Fact] + public async Task ReadsAndWritesAfterReset() + { + var source = new byte[] { 1, 2, 3 }; + + await _pipe.Writer.WriteAsync(source); + ReadResult result = await _pipe.Reader.ReadAsync(); + + Assert.Equal(source, result.Buffer.ToArray()); + _pipe.Reader.AdvanceTo(result.Buffer.End); + + _pipe.Reader.Complete(); + _pipe.Writer.Complete(); + + _pipe.Reset(); + + await _pipe.Writer.WriteAsync(source); + result = await _pipe.Reader.ReadAsync(); + + Assert.Equal(source, result.Buffer.ToArray()); + _pipe.Reader.AdvanceTo(result.Buffer.End); + } + + [Fact] + public void ResetThrowsIfReaderNotCompleted() + { + _pipe.Writer.Complete(); + Assert.Throws(() => _pipe.Reset()); + } + + [Fact] + public void ResetThrowsIfWriterNotCompleted() + { + _pipe.Reader.Complete(); + Assert.Throws(() => _pipe.Reset()); + } + } +} diff --git a/src/System.IO.Pipelines/tests/PipeTest.cs b/src/System.IO.Pipelines/tests/PipeTest.cs new file mode 100644 index 000000000000..425d6521d741 --- /dev/null +++ b/src/System.IO.Pipelines/tests/PipeTest.cs @@ -0,0 +1,35 @@ +// 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. + +namespace System.IO.Pipelines.Tests +{ + public abstract class PipeTest : IDisposable + { + protected const int MaximumSizeHigh = 65; + + protected const int MaximumSizeLow = 6; + + private readonly TestMemoryPool _pool; + + protected Pipe Pipe; + + protected PipeTest(int pauseWriterThreshold = MaximumSizeHigh, int resumeWriterThreshold = MaximumSizeLow) + { + _pool = new TestMemoryPool(); + Pipe = new Pipe( + new PipeOptions( + _pool, + pauseWriterThreshold: pauseWriterThreshold, + resumeWriterThreshold: resumeWriterThreshold + )); + } + + public void Dispose() + { + Pipe.Writer.Complete(); + Pipe.Reader.Complete(); + _pool.Dispose(); + } + } +} diff --git a/src/System.IO.Pipelines/tests/PipeWriterTests.cs b/src/System.IO.Pipelines/tests/PipeWriterTests.cs new file mode 100644 index 000000000000..5f3ea2336d7c --- /dev/null +++ b/src/System.IO.Pipelines/tests/PipeWriterTests.cs @@ -0,0 +1,210 @@ +// 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.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class PipeWriterTests : PipeTest + { + public PipeWriterTests() : base(0, 0) + { + } + + private byte[] Read() + { + Pipe.Writer.FlushAsync().GetAwaiter().GetResult(); + ReadResult readResult = Pipe.Reader.ReadAsync().GetAwaiter().GetResult(); + byte[] data = readResult.Buffer.ToArray(); + Pipe.Reader.AdvanceTo(readResult.Buffer.End); + return data; + } + + [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) + { + OutputWriter writer = OutputWriter.Create(Pipe.Writer); + var array = new byte[arrayLength]; + for (var i = 0; i < array.Length; i++) + { + array[i] = (byte)(i + 1); + } + + writer.Write(new Span(array, 0, 0)); + writer.Write(new Span(array, array.Length, 0)); + + try + { + writer.Write(new Span(array, offset, length)); + Assert.True(false); + } + catch (Exception ex) + { + Assert.True(ex is ArgumentOutOfRangeException); + } + + writer.Write(new Span(array, 0, array.Length)); + Assert.Equal(array, Read()); + } + + [Theory] + [InlineData(0, 0, 3)] + [InlineData(0, 1, 2)] + [InlineData(0, 2, 1)] + [InlineData(0, 1, 1)] + [InlineData(1, 0, 3)] + [InlineData(1, 1, 2)] + [InlineData(1, 2, 1)] + [InlineData(1, 1, 1)] + public void CanWriteWithOffsetAndLenght(int alloc, int offset, int length) + { + OutputWriter writer = OutputWriter.Create(Pipe.Writer); + var array = new byte[] { 1, 2, 3 }; + + writer.Write(new Span(array, offset, length)); + + Assert.Equal(array.Skip(offset).Take(length).ToArray(), Read()); + } + + [Fact] + public void CanWriteEmpty() + { + OutputWriter writer = OutputWriter.Create(Pipe.Writer); + var array = new byte[] { }; + + writer.Write(array); + writer.Write(new Span(array, 0, array.Length)); + + Assert.Equal(array, Read()); + } + + [Fact] + public void CanWriteIntoHeadlessBuffer() + { + OutputWriter writer = OutputWriter.Create(Pipe.Writer); + + writer.Write(new byte[] { 1, 2, 3 }); + Assert.Equal(new byte[] { 1, 2, 3 }, Read()); + } + + [Fact] + public void CanWriteMultipleTimes() + { + OutputWriter writer = OutputWriter.Create(Pipe.Writer); + + 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 memory = Pipe.Writer.GetMemory(); + OutputWriter writer = OutputWriter.Create(Pipe.Writer); + + IEnumerable 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() + { + OutputWriter writer = OutputWriter.Create(Pipe.Writer); + writer.Ensure(10); + + Assert.True(writer.Span.Length > 10); + Assert.Equal(new byte[] { }, Read()); + } + + [Fact] + public void ExposesSpan() + { + int initialLength = Pipe.Writer.GetMemory().Length; + OutputWriter writer = OutputWriter.Create(Pipe.Writer); + Assert.Equal(initialLength, writer.Span.Length); + Assert.Equal(new byte[] { }, Read()); + } + + [Fact] + public void SlicesSpanAndAdvancesAfterWrite() + { + int initialLength = Pipe.Writer.GetMemory().Length; + + OutputWriter writer = OutputWriter.Create(Pipe.Writer); + + writer.Write(new byte[] { 1, 2, 3 }); + + Assert.Equal(initialLength - 3, writer.Span.Length); + Assert.Equal(Pipe.Writer.GetMemory().Length, writer.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 = Pipe.Writer; + output.Write(data); + await output.FlushAsync(); + + ReadResult result = await Pipe.Reader.ReadAsync(); + ReadOnlySequence input = result.Buffer; + Assert.Equal(data, input.ToArray()); + Pipe.Reader.AdvanceTo(input.End); + } + + [Fact] + public async Task CanWriteNothingToBuffer() + { + PipeWriter buffer = Pipe.Writer; + buffer.GetMemory(0); + buffer.Advance(0); // doing nothing, the hard way + await buffer.FlushAsync(); + } + + [Fact] + public void EmptyWriteDoesNotThrow() + { + Pipe.Writer.Write(new byte[0]); + } + + [Fact] + public void ThrowsOnAdvanceOverMemorySize() + { + Memory buffer = Pipe.Writer.GetMemory(1); + var exception = Assert.Throws(() => Pipe.Writer.Advance(buffer.Length + 1)); + Assert.Equal("Can't advance past buffer size", exception.Message); + } + + [Fact] + public void ThrowsOnAdvanceWithNoMemory() + { + PipeWriter buffer = Pipe.Writer; + var exception = Assert.Throws(() => buffer.Advance(1)); + Assert.Equal("No writing operation. Make sure GetMemory() was called.", exception.Message); + } + } +} diff --git a/src/System.IO.Pipelines/tests/PipelineReaderWriterFacts.cs b/src/System.IO.Pipelines/tests/PipelineReaderWriterFacts.cs new file mode 100644 index 000000000000..e069e856af7c --- /dev/null +++ b/src/System.IO.Pipelines/tests/PipelineReaderWriterFacts.cs @@ -0,0 +1,561 @@ +// 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.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class PipelineReaderWriterFacts : IDisposable + { + public PipelineReaderWriterFacts() + { + _pool = new TestMemoryPool(); + _pipe = new Pipe(new PipeOptions(_pool)); + } + + public void Dispose() + { + _pipe.Writer.Complete(); + _pipe.Reader.Complete(); + _pool?.Dispose(); + } + + private readonly Pipe _pipe; + + private readonly TestMemoryPool _pool; + + [Fact] + public async Task AdvanceEmptyBufferAfterWritingResetsAwaitable() + { + byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); + + await _pipe.Writer.WriteAsync(bytes); + ReadResult result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.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)); + + _pipe.Reader.AdvanceTo(buffer.End); + + // Now write 0 and advance 0 + await _pipe.Writer.WriteAsync(new byte[] { }); + result = await _pipe.Reader.ReadAsync(); + _pipe.Reader.AdvanceTo(result.Buffer.End); + + PipeAwaiter awaitable = _pipe.Reader.ReadAsync(); + Assert.False(awaitable.IsCompleted); + } + + [Fact] + public async Task AdvanceResetsCommitHeadIndex() + { + _pipe.Writer.GetMemory(1); + _pipe.Writer.Advance(100); + await _pipe.Writer.FlushAsync(); + + // Advance to the end + ReadResult readResult = await _pipe.Reader.ReadAsync(); + _pipe.Reader.AdvanceTo(readResult.Buffer.End); + + // Try reading, it should block + PipeAwaiter awaitable = _pipe.Reader.ReadAsync(); + Assert.False(awaitable.IsCompleted); + + // Unblock without writing anything + _pipe.Writer.GetMemory(); + await _pipe.Writer.FlushAsync(); + + Assert.True(awaitable.IsCompleted); + + // Advance to the end should reset awaitable + readResult = await awaitable; + _pipe.Reader.AdvanceTo(readResult.Buffer.End); + + // Try reading, it should block + awaitable = _pipe.Reader.ReadAsync(); + Assert.False(awaitable.IsCompleted); + } + + [Fact] + public async Task AdvanceShouldResetStateIfReadCancelled() + { + _pipe.Reader.CancelPendingRead(); + + ReadResult result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + _pipe.Reader.AdvanceTo(buffer.End); + + Assert.False(result.IsCompleted); + Assert.True(result.IsCanceled); + Assert.True(buffer.IsEmpty); + + PipeAwaiter awaitable = _pipe.Reader.ReadAsync(); + Assert.False(awaitable.IsCompleted); + } + + [Fact] + public async Task AdvanceToInvalidCursorThrows() + { + await _pipe.Writer.WriteAsync(new byte[100]); + + ReadResult result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + + _pipe.Reader.AdvanceTo(buffer.End); + + _pipe.Reader.CancelPendingRead(); + result = await _pipe.Reader.ReadAsync(); + + Assert.Throws(() => _pipe.Reader.AdvanceTo(buffer.End)); + _pipe.Reader.AdvanceTo(result.Buffer.End); + } + + [Fact] + public async Task AdvanceWithGetPositionCrossingIntoWriteHeadWorks() + { + // Create two blocks + Memory memory = _pipe.Writer.GetMemory(1); + _pipe.Writer.Advance(memory.Length); + memory = _pipe.Writer.GetMemory(1); + _pipe.Writer.Advance(memory.Length); + await _pipe.Writer.FlushAsync(); + + // Read single block + ReadResult readResult = await _pipe.Reader.ReadAsync(); + + // Allocate more memory + memory = _pipe.Writer.GetMemory(1); + + // Create position that would cross into write head + ReadOnlySequence buffer = readResult.Buffer; + SequencePosition position = buffer.GetPosition(buffer.Start, buffer.Length); + + // Return everything + _pipe.Reader.AdvanceTo(position); + + // Advance writer + _pipe.Writer.Advance(memory.Length); + _pipe.Writer.Commit(); + } + + [Fact] + public async Task CompleteReaderThrowsIfReadInProgress() + { + await _pipe.Writer.WriteAsync(new byte[1]); + ReadResult result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + + Assert.Throws(() => _pipe.Reader.Complete()); + + _pipe.Reader.AdvanceTo(buffer.Start, buffer.Start); + } + + [Fact] + public async Task EmptyBufferStartCrossingSegmentBoundaryIsTreatedLikeAndEnd() + { + Memory memory = _pipe.Writer.GetMemory(); + // Append one full segment to a pipe + _pipe.Writer.Write(memory.Span); + _pipe.Writer.Commit(); + await _pipe.Writer.FlushAsync(); + + // Consume entire segment + ReadResult result = await _pipe.Reader.ReadAsync(); + _pipe.Reader.AdvanceTo(result.Buffer.End); + + // Append empty segment + _pipe.Writer.GetMemory(1); + _pipe.Writer.Commit(); + await _pipe.Writer.FlushAsync(); + + result = await _pipe.Reader.ReadAsync(); + + Assert.True(result.Buffer.IsEmpty); + Assert.Equal(result.Buffer.Start, result.Buffer.End); + + _pipe.Writer.GetMemory(); + _pipe.Reader.AdvanceTo(result.Buffer.Start); + PipeAwaiter awaitable = _pipe.Reader.ReadAsync(); + Assert.False(awaitable.IsCompleted); + _pipe.Writer.Commit(); + } + + [Fact] + public void FlushAsync_ReturnsCompletedTaskWhenMaxSizeIfZero() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(1); + PipeAwaiter flushTask = writableBuffer.FlushAsync(); + Assert.True(flushTask.IsCompleted); + + writableBuffer = _pipe.Writer.WriteEmpty(1); + flushTask = writableBuffer.FlushAsync(); + Assert.True(flushTask.IsCompleted); + } + + [Fact] + public async Task FlushAsync_ThrowsIfWriterReaderWithException() + { + void ThrowTestException() + { + try + { + throw new InvalidOperationException("Reader exception"); + } + catch (Exception e) + { + _pipe.Reader.Complete(e); + } + } + + ThrowTestException(); + + InvalidOperationException invalidOperationException = + await Assert.ThrowsAsync(async () => await _pipe.Writer.FlushAsync()); + + Assert.Equal("Reader exception", invalidOperationException.Message); + Assert.Contains("ThrowTestException", invalidOperationException.StackTrace); + + invalidOperationException = await Assert.ThrowsAsync(async () => await _pipe.Writer.FlushAsync()); + Assert.Equal("Reader exception", invalidOperationException.Message); + Assert.Contains("ThrowTestException", invalidOperationException.StackTrace); + } + + [Fact] + public async Task HelloWorldAcrossTwoBlocks() + { + // block 1 -> block2 + // [padding..hello] -> [ world ] + PipeWriter writeBuffer = _pipe.Writer; + var blockSize = _pipe.Writer.GetMemory(0).Length; + + byte[] paddingBytes = Enumerable.Repeat((byte)'a', blockSize - 5).ToArray(); + byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); + + writeBuffer.Write(paddingBytes); + writeBuffer.Write(bytes); + await writeBuffer.FlushAsync(); + + ReadResult result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + Assert.False(buffer.IsSingleSegment); + ReadOnlySequence helloBuffer = buffer.Slice(blockSize - 5); + Assert.False(helloBuffer.IsSingleSegment); + var memory = new List>(); + foreach (ReadOnlyMemory m in helloBuffer) + { + memory.Add(m); + } + + List> spans = memory; + _pipe.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 ReadAsync_ThrowsIfWriterCompletedWithException() + { + void ThrowTestException() + { + try + { + throw new InvalidOperationException("Writer exception"); + } + catch (Exception e) + { + _pipe.Writer.Complete(e); + } + } + + ThrowTestException(); + + InvalidOperationException invalidOperationException = + await Assert.ThrowsAsync(async () => await _pipe.Reader.ReadAsync()); + + Assert.Equal("Writer exception", invalidOperationException.Message); + Assert.Contains("ThrowTestException", invalidOperationException.StackTrace); + + invalidOperationException = await Assert.ThrowsAsync(async () => await _pipe.Reader.ReadAsync()); + Assert.Equal("Writer exception", invalidOperationException.Message); + Assert.Contains("ThrowTestException", invalidOperationException.StackTrace); + } + + [Fact] + public async Task ReaderShouldNotGetUnflushedBytes() + { + // Write 10 and flush + PipeWriter buffer = _pipe.Writer; + buffer.Write(new byte[] { 0, 0, 0, 10 }); + await buffer.FlushAsync(); + + // Write 9 + buffer = _pipe.Writer; + buffer.Write(new byte[] { 0, 0, 0, 9 }); + + // Write 8 + buffer.Write(new byte[] { 0, 0, 0, 8 }); + + // Make sure we don't see it yet + ReadResult result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence reader = result.Buffer; + + Assert.Equal(4, reader.Length); + Assert.Equal(new byte[] { 0, 0, 0, 10 }, reader.ToArray()); + + // Don't move + _pipe.Reader.AdvanceTo(reader.Start); + + // Now flush + await buffer.FlushAsync(); + + reader = (await _pipe.Reader.ReadAsync()).Buffer; + + Assert.Equal(12, reader.Length); + Assert.Equal(new byte[] { 0, 0, 0, 10 }, reader.Slice(0, 4).ToArray()); + Assert.Equal(new byte[] { 0, 0, 0, 9 }, reader.Slice(4, 4).ToArray()); + Assert.Equal(new byte[] { 0, 0, 0, 8 }, reader.Slice(8, 4).ToArray()); + + _pipe.Reader.AdvanceTo(reader.Start, reader.Start); + } + + [Fact] + public async Task ReaderShouldNotGetUnflushedBytesWhenOverflowingSegments() + { + // Fill the block with stuff leaving 5 bytes at the end + Memory buffer = _pipe.Writer.GetMemory(); + + int len = buffer.Length; + // Fill the buffer with garbage + // block 1 -> block2 + // [padding..hello] -> [ world ] + byte[] paddingBytes = Enumerable.Repeat((byte)'a', len - 5).ToArray(); + _pipe.Writer.Write(paddingBytes); + await _pipe.Writer.FlushAsync(); + + // Write 10 and flush + _pipe.Writer.Write(new byte[] { 0, 0, 0, 10 }); + + // Write 9 + _pipe.Writer.Write(new byte[] { 0, 0, 0, 9 }); + + // Write 8 + _pipe.Writer.Write(new byte[] { 0, 0, 0, 8 }); + + // Make sure we don't see it yet + ReadResult result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence reader = result.Buffer; + + Assert.Equal(len - 5, reader.Length); + + // Don't move + _pipe.Reader.AdvanceTo(reader.End); + + // Now flush + await _pipe.Writer.FlushAsync(); + + reader = (await _pipe.Reader.ReadAsync()).Buffer; + + Assert.Equal(12, reader.Length); + Assert.Equal(new byte[] { 0, 0, 0, 10 }, reader.Slice(0, 4).ToArray()); + Assert.Equal(new byte[] { 0, 0, 0, 9 }, reader.Slice(4, 4).ToArray()); + Assert.Equal(new byte[] { 0, 0, 0, 8 }, reader.Slice(8, 4).ToArray()); + + _pipe.Reader.AdvanceTo(reader.Start, reader.Start); + } + + [Fact] + public async Task ReaderShouldNotGetUnflushedBytesWithAppend() + { + // Write 10 and flush + PipeWriter buffer = _pipe.Writer; + buffer.Write(new byte[] { 0, 0, 0, 10 }); + await buffer.FlushAsync(); + + // Write Hello to another pipeline and get the buffer + byte[] bytes = Encoding.ASCII.GetBytes("Hello"); + + var c2 = new Pipe(new PipeOptions(_pool)); + await c2.Writer.WriteAsync(bytes); + ReadResult result = await c2.Reader.ReadAsync(); + ReadOnlySequence c2Buffer = result.Buffer; + + Assert.Equal(bytes.Length, c2Buffer.Length); + + // Write 9 to the buffer + buffer = _pipe.Writer; + buffer.Write(new byte[] { 0, 0, 0, 9 }); + + // Append the data from the other pipeline + foreach (ReadOnlyMemory memory in c2Buffer) + { + buffer.Write(memory.Span); + } + + // Mark it as consumed + c2.Reader.AdvanceTo(c2Buffer.End); + + // Now read and make sure we only see the comitted data + result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence reader = result.Buffer; + + Assert.Equal(4, reader.Length); + Assert.Equal(new byte[] { 0, 0, 0, 10 }, reader.Slice(0, 4).ToArray()); + + // Consume nothing + _pipe.Reader.AdvanceTo(reader.Start); + + // Flush the second set of writes + await buffer.FlushAsync(); + + reader = (await _pipe.Reader.ReadAsync()).Buffer; + + // int, int, "Hello" + Assert.Equal(13, reader.Length); + Assert.Equal(new byte[] { 0, 0, 0, 10 }, reader.Slice(0, 4).ToArray()); + Assert.Equal(new byte[] { 0, 0, 0, 9 }, reader.Slice(4, 4).ToArray()); + Assert.Equal("Hello", Encoding.ASCII.GetString(reader.Slice(8).ToArray())); + + _pipe.Reader.AdvanceTo(reader.Start, reader.Start); + } + + [Fact] + public async Task ReadingCanBeCancelled() + { + var cts = new CancellationTokenSource(); + cts.Token.Register(() => { _pipe.Writer.Complete(new OperationCanceledException(cts.Token)); }); + + Task ignore = Task.Run( + async () => { + await Task.Delay(1000); + cts.Cancel(); + }); + + await Assert.ThrowsAsync( + async () => { + ReadResult result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + }); + } + + [Fact] + public async Task SyncReadThenAsyncRead() + { + PipeWriter buffer = _pipe.Writer; + buffer.Write(Encoding.ASCII.GetBytes("Hello World")); + await buffer.FlushAsync(); + + bool gotData = _pipe.Reader.TryRead(out ReadResult result); + Assert.True(gotData); + + Assert.Equal("Hello World", Encoding.ASCII.GetString(result.Buffer.ToArray())); + + _pipe.Reader.AdvanceTo(result.Buffer.GetPosition(result.Buffer.Start, 6)); + + result = await _pipe.Reader.ReadAsync(); + + Assert.Equal("World", Encoding.ASCII.GetString(result.Buffer.ToArray())); + + _pipe.Reader.AdvanceTo(result.Buffer.End); + } + + [Fact] + public void ThrowsOnAllocAfterCompleteWriter() + { + _pipe.Writer.Complete(); + + Assert.Throws(() => _pipe.Writer.GetMemory()); + } + + [Fact] + public void ThrowsOnReadAfterCompleteReader() + { + _pipe.Reader.Complete(); + + Assert.Throws(() => _pipe.Reader.ReadAsync()); + } + + [Fact] + public void TryReadAfterCancelPendingReadReturnsTrue() + { + _pipe.Reader.CancelPendingRead(); + + bool gotData = _pipe.Reader.TryRead(out ReadResult result); + + Assert.True(result.IsCanceled); + + _pipe.Reader.AdvanceTo(result.Buffer.End); + } + + [Fact] + public void TryReadAfterCloseWriterWithExceptionThrows() + { + _pipe.Writer.Complete(new Exception("wow")); + + var ex = Assert.Throws(() => _pipe.Reader.TryRead(out ReadResult result)); + Assert.Equal("wow", ex.Message); + } + + [Fact] + public void TryReadAfterReaderCompleteThrows() + { + _pipe.Reader.Complete(); + + Assert.Throws(() => _pipe.Reader.TryRead(out ReadResult result)); + } + + [Fact] + public void TryReadAfterWriterCompleteReturnsTrue() + { + _pipe.Writer.Complete(); + + bool gotData = _pipe.Reader.TryRead(out ReadResult result); + + Assert.True(result.IsCompleted); + + _pipe.Reader.AdvanceTo(result.Buffer.End); + } + + [Fact] + public void WhenTryReadReturnsFalseDontNeedToCallAdvance() + { + bool gotData = _pipe.Reader.TryRead(out ReadResult result); + Assert.False(gotData); + _pipe.Reader.AdvanceTo(default); + } + + [Fact] + public async Task WritingDataMakesDataReadableViaPipeline() + { + byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); + + await _pipe.Writer.WriteAsync(bytes); + ReadResult result = await _pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.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)); + + _pipe.Reader.AdvanceTo(buffer.Start, buffer.Start); + } + } +} diff --git a/src/System.IO.Pipelines/tests/ReadAsyncCancellationTests.cs b/src/System.IO.Pipelines/tests/ReadAsyncCancellationTests.cs new file mode 100644 index 000000000000..94a8fd10701c --- /dev/null +++ b/src/System.IO.Pipelines/tests/ReadAsyncCancellationTests.cs @@ -0,0 +1,433 @@ +// 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.Buffers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class ReadAsyncCancellationTests : PipeTest + { + [Fact] + public async Task AdvanceShouldResetStateIfReadCancelled() + { + Pipe.Reader.CancelPendingRead(); + + ReadResult result = await Pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + Pipe.Reader.AdvanceTo(buffer.End); + + Assert.False(result.IsCompleted); + Assert.True(result.IsCanceled); + Assert.True(buffer.IsEmpty); + + PipeAwaiter awaitable = Pipe.Reader.ReadAsync(); + Assert.False(awaitable.IsCompleted); + } + + [Fact] + public async Task CancellingBeforeAdvance() + { + byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); + PipeWriter output = Pipe.Writer; + output.Write(bytes); + await output.FlushAsync(); + + ReadResult result = await Pipe.Reader.ReadAsync(); + ReadOnlySequence 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)); + + Pipe.Reader.CancelPendingRead(); + + Pipe.Reader.AdvanceTo(buffer.End); + + PipeAwaiter awaitable = Pipe.Reader.ReadAsync(); + + Assert.True(awaitable.IsCompleted); + + result = await awaitable; + + Assert.True(result.IsCanceled); + + Pipe.Reader.AdvanceTo(result.Buffer.Start, result.Buffer.Start); + } + + [Fact] + public async Task CancellingPendingAfterReadAsync() + { + byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); + PipeWriter output = Pipe.Writer; + output.Write(bytes); + + Func taskFunc = async () => { + ReadResult result = await Pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + Pipe.Reader.AdvanceTo(buffer.End); + + Assert.False(result.IsCompleted); + Assert.True(result.IsCanceled); + Assert.True(buffer.IsEmpty); + + await output.FlushAsync(); + + result = await Pipe.Reader.ReadAsync(); + buffer = result.Buffer; + + Assert.Equal(11, buffer.Length); + Assert.True(buffer.IsSingleSegment); + Assert.False(result.IsCanceled); + var array = new byte[11]; + buffer.First.Span.CopyTo(array); + Assert.Equal("Hello World", Encoding.ASCII.GetString(array)); + Pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); + + Pipe.Reader.Complete(); + }; + + Task task = taskFunc(); + + Pipe.Reader.CancelPendingRead(); + + await task; + + Pipe.Writer.Complete(); + } + + [Fact] + public async Task CancellingPendingReadBeforeReadAsync() + { + Pipe.Reader.CancelPendingRead(); + + ReadResult result = await Pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + Pipe.Reader.AdvanceTo(buffer.End); + + Assert.False(result.IsCompleted); + Assert.True(result.IsCanceled); + Assert.True(buffer.IsEmpty); + + byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); + PipeWriter output = Pipe.Writer; + output.Write(bytes); + await output.FlushAsync(); + + result = await Pipe.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)); + + Pipe.Reader.AdvanceTo(buffer.Start, buffer.Start); + } + + [Fact] + public void FlushAsyncCancellationDeadlock() + { + var cts = new CancellationTokenSource(); + var cts2 = new CancellationTokenSource(); + var e = new ManualResetEventSlim(); + + PipeAwaiter awaiter = Pipe.Reader.ReadAsync(cts.Token); + awaiter.OnCompleted( + () => { + // We are on cancellation thread and need to wait untill another ReadAsync call + // takes pipe state lock + e.Wait(); + // Make sure we had enough time to reach _cancellationTokenRegistration.Dispose + Thread.Sleep(100); + // Try to take pipe state lock + Pipe.Reader.ReadAsync(); + }); + + // Start a thread that would run cancellation calbacks + Task cancellationTask = Task.Run(() => cts.Cancel()); + // Start a thread that would call ReadAsync with different token + // and block on _cancellationTokenRegistration.Dispose + Task blockingTask = Task.Run( + () => { + e.Set(); + Pipe.Reader.ReadAsync(cts2.Token); + }); + + bool completed = Task.WhenAll(cancellationTask, blockingTask).Wait(TimeSpan.FromSeconds(10)); + Assert.True(completed); + } + + [Fact] + public void GetResultThrowsIfFlushAsyncTokenFiredAfterCancelPending() + { + var onCompletedCalled = false; + var cancellationTokenSource = new CancellationTokenSource(); + + PipeAwaiter awaiter = Pipe.Reader.ReadAsync(cancellationTokenSource.Token); + bool awaiterIsCompleted = awaiter.IsCompleted; + + cancellationTokenSource.Cancel(); + Pipe.Reader.CancelPendingRead(); + + awaiter.OnCompleted( + () => { + onCompletedCalled = true; + Assert.Throws(() => awaiter.GetResult()); + }); + + Assert.False(awaiterIsCompleted); + Assert.True(onCompletedCalled); + } + + [Fact] + public void GetResultThrowsIfReadAsyncCancelledAfterOnCompleted() + { + var onCompletedCalled = false; + var cancellationTokenSource = new CancellationTokenSource(); + + PipeAwaiter awaiter = Pipe.Reader.ReadAsync(cancellationTokenSource.Token); + bool awaiterIsCompleted = awaiter.IsCompleted; + awaiter.OnCompleted( + () => { + onCompletedCalled = true; + Assert.Throws(() => awaiter.GetResult()); + }); + + cancellationTokenSource.Cancel(); + + Assert.False(awaiterIsCompleted); + Assert.True(onCompletedCalled); + } + + [Fact] + public void GetResultThrowsIfReadAsyncCancelledBeforeOnCompleted() + { + var onCompletedCalled = false; + var cancellationTokenSource = new CancellationTokenSource(); + + PipeAwaiter awaiter = Pipe.Reader.ReadAsync(cancellationTokenSource.Token); + bool awaiterIsCompleted = awaiter.IsCompleted; + + cancellationTokenSource.Cancel(); + + awaiter.OnCompleted( + () => { + onCompletedCalled = true; + Assert.Throws(() => awaiter.GetResult()); + }); + + Assert.False(awaiterIsCompleted); + Assert.True(onCompletedCalled); + } + + [Fact] + public async Task ReadAsyncCancellationE2E() + { + var cts = new CancellationTokenSource(); + var e = new AutoResetEvent(false); + var cancelled = false; + + Func taskFunc = async () => { + try + { + ReadResult result = await Pipe.Reader.ReadAsync(cts.Token); + } + catch (OperationCanceledException) + { + cancelled = true; + ReadResult result = await Pipe.Reader.ReadAsync(); + Assert.Equal(new byte[] { 1, 2, 3 }, result.Buffer.ToArray()); + Pipe.Reader.AdvanceTo(result.Buffer.End); + } + }; + + Task task = taskFunc(); + + cts.Cancel(); + + await Pipe.Writer.WriteAsync(new byte[] { 1, 2, 3 }); + await task; + Assert.True(cancelled); + } + + [Fact] + public void ReadAsyncCompletedAfterPreCancellation() + { + Pipe.Reader.CancelPendingRead(); + Pipe.Writer.WriteAsync(new byte[] { 1, 2, 3 }).GetAwaiter().GetResult(); + + PipeAwaiter awaitable = Pipe.Reader.ReadAsync(); + + Assert.True(awaitable.IsCompleted); + + ReadResult result = awaitable.GetResult(); + + Assert.True(result.IsCanceled); + + awaitable = Pipe.Reader.ReadAsync(); + + Assert.True(awaitable.IsCompleted); + + Pipe.Reader.AdvanceTo(awaitable.GetResult().Buffer.End); + } + + [Fact] + public void ReadAsyncNotCompletedAfterCancellation() + { + var onCompletedCalled = false; + PipeAwaiter awaitable = Pipe.Reader.ReadAsync(); + + Assert.False(awaitable.IsCompleted); + awaitable.OnCompleted( + () => { + onCompletedCalled = true; + Assert.True(awaitable.IsCompleted); + + ReadResult readResult = awaitable.GetResult(); + Assert.True(readResult.IsCanceled); + + awaitable = Pipe.Reader.ReadAsync(); + Assert.False(awaitable.IsCompleted); + }); + + Pipe.Reader.CancelPendingRead(); + Assert.True(onCompletedCalled); + } + + [Fact] + public void ReadAsyncNotCompletedAfterCancellationTokenCancelled() + { + var onCompletedCalled = false; + var cts = new CancellationTokenSource(); + PipeAwaiter awaitable = Pipe.Reader.ReadAsync(cts.Token); + + Assert.False(awaitable.IsCompleted); + awaitable.OnCompleted( + () => { + onCompletedCalled = true; + Assert.True(awaitable.IsCompleted); + + Assert.Throws(() => awaitable.GetResult()); + + awaitable = Pipe.Reader.ReadAsync(); + Assert.False(awaitable.IsCompleted); + }); + + cts.Cancel(); + Assert.True(onCompletedCalled); + } + + [Fact] + public void ReadAsyncReturnsIsCancelOnCancelPendingReadAfterGetResult() + { + PipeAwaiter awaitable = Pipe.Reader.ReadAsync(); + + Assert.False(awaitable.IsCompleted); + awaitable.OnCompleted(() => { }); + + Pipe.Writer.WriteAsync(new byte[] { }); + Pipe.Reader.CancelPendingRead(); + + Assert.True(awaitable.IsCompleted); + + ReadResult result = awaitable.GetResult(); + Assert.True(result.IsCanceled); + } + + [Fact] + public void ReadAsyncReturnsIsCancelOnCancelPendingReadBeforeGetResult() + { + PipeAwaiter awaitable = Pipe.Reader.ReadAsync(); + + Assert.False(awaitable.IsCompleted); + awaitable.OnCompleted(() => { }); + + Pipe.Writer.WriteAsync(new byte[] { }); + Pipe.Reader.CancelPendingRead(); + + Assert.True(awaitable.IsCompleted); + + ReadResult result = awaitable.GetResult(); + Assert.True(result.IsCanceled); + } + + [Fact] + public void ReadAsyncThrowsIfPassedCancelledCancellationToken() + { + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + + Assert.Throws(() => Pipe.Reader.ReadAsync(cancellationTokenSource.Token)); + } + + [Fact] + public async Task ReadAsyncWithNewCancellationTokenNotAffectedByPrevious() + { + await Pipe.Writer.WriteAsync(new byte[] { 0 }); + + var cancellationTokenSource1 = new CancellationTokenSource(); + ReadResult result = await Pipe.Reader.ReadAsync(cancellationTokenSource1.Token); + Pipe.Reader.AdvanceTo(result.Buffer.Start); + + cancellationTokenSource1.Cancel(); + var cancellationTokenSource2 = new CancellationTokenSource(); + + // Verifying that ReadAsync does not throw + result = await Pipe.Reader.ReadAsync(cancellationTokenSource2.Token); + Pipe.Reader.AdvanceTo(result.Buffer.Start); + } + + [Fact] + public async Task ReadingCanBeCancelled() + { + var cts = new CancellationTokenSource(); + cts.Token.Register(() => { Pipe.Writer.Complete(new OperationCanceledException(cts.Token)); }); + + Task ignore = Task.Run( + async () => { + await Task.Delay(1000); + cts.Cancel(); + }); + + await Assert.ThrowsAsync( + async () => { + ReadResult result = await Pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + }); + } + + [Fact] + public async Task WriteAndCancellingPendingReadBeforeReadAsync() + { + byte[] bytes = Encoding.ASCII.GetBytes("Hello World"); + PipeWriter output = Pipe.Writer; + output.Write(bytes); + await output.FlushAsync(); + + Pipe.Reader.CancelPendingRead(); + + ReadResult result = await Pipe.Reader.ReadAsync(); + ReadOnlySequence buffer = result.Buffer; + + Assert.False(result.IsCompleted); + Assert.True(result.IsCanceled); + Assert.False(buffer.IsEmpty); + 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)); + Pipe.Reader.AdvanceTo(buffer.End, buffer.End); + } + } +} diff --git a/src/System.IO.Pipelines/tests/ReadAsyncCompletionTests.cs b/src/System.IO.Pipelines/tests/ReadAsyncCompletionTests.cs new file mode 100644 index 000000000000..040b1ddbe54c --- /dev/null +++ b/src/System.IO.Pipelines/tests/ReadAsyncCompletionTests.cs @@ -0,0 +1,34 @@ +// 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.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class ReadAsyncCompletionTests : PipeTest + { + [Fact] + public void AwaitingReadAsyncAwaitableTwiceCompletesWriterWithException() + { + async Task Await(PipeAwaiter a) + { + await a; + } + + PipeAwaiter awaitable = Pipe.Reader.ReadAsync(); + + Task task1 = Await(awaitable); + Task task2 = Await(awaitable); + + Assert.Equal(true, task1.IsCompleted); + Assert.Equal(true, task1.IsFaulted); + Assert.Equal("Concurrent reads or writes are not supported.", task1.Exception.InnerExceptions[0].Message); + + Assert.Equal(true, task2.IsCompleted); + Assert.Equal(true, task2.IsFaulted); + Assert.Equal("Concurrent reads or writes are not supported.", task2.Exception.InnerExceptions[0].Message); + } + } +} diff --git a/src/System.IO.Pipelines/tests/ReadResultTests.cs b/src/System.IO.Pipelines/tests/ReadResultTests.cs new file mode 100644 index 000000000000..61ab26880767 --- /dev/null +++ b/src/System.IO.Pipelines/tests/ReadResultTests.cs @@ -0,0 +1,27 @@ +// 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.Buffers; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class ReadResultTests + { + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + [Theory] + public void ReadResultCanBeConstructed(bool cancelled, bool completed) + { + var buffer = new ReadOnlySequence(new byte[] { 1, 2, 3 }); + var result = new ReadResult(buffer, cancelled, completed); + + Assert.Equal(new byte[] { 1, 2, 3 }, result.Buffer.ToArray()); + Assert.Equal(cancelled, result.IsCanceled); + Assert.Equal(completed, result.IsCompleted); + } + } +} diff --git a/src/System.IO.Pipelines/tests/SchedulerFacts.cs b/src/System.IO.Pipelines/tests/SchedulerFacts.cs new file mode 100644 index 000000000000..c3bd3f12e9b1 --- /dev/null +++ b/src/System.IO.Pipelines/tests/SchedulerFacts.cs @@ -0,0 +1,204 @@ +// 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.Buffers; +using System.Collections.Concurrent; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class SchedulerFacts + { + private class ThreadScheduler : PipeScheduler, IDisposable + { + private readonly BlockingCollection _work = new BlockingCollection(); + + public ThreadScheduler() + { + Thread = new Thread(Work) { IsBackground = true }; + Thread.Start(); + } + + public Thread Thread { get; } + + public void Dispose() + { + _work.CompleteAdding(); + } + + public override void Schedule(Action action) + { + Schedule(o => ((Action)o)(), action); + } + + public override void Schedule(Action action, object state) + { + _work.Add(() => action(state)); + } + + private void Work(object state) + { + foreach (Action callback in _work.GetConsumingEnumerable()) + { + callback(); + } + } + } + + [Fact] + public async Task DefaultReaderSchedulerRunsInline() + { + using (var pool = new TestMemoryPool()) + { + var pipe = new Pipe(new PipeOptions(pool)); + + var id = 0; + + Func doRead = async () => { + ReadResult result = await pipe.Reader.ReadAsync(); + + Assert.Equal(Thread.CurrentThread.ManagedThreadId, id); + + pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); + + pipe.Reader.Complete(); + }; + + Task reading = doRead(); + + id = Thread.CurrentThread.ManagedThreadId; + + PipeWriter buffer = pipe.Writer; + buffer.Write(Encoding.UTF8.GetBytes("Hello World")); + await buffer.FlushAsync(); + + pipe.Writer.Complete(); + + await reading; + } + } + + [Fact] + public async Task DefaultWriterSchedulerRunsInline() + { + using (var pool = new TestMemoryPool()) + { + var pipe = new Pipe( + new PipeOptions( + pool, + resumeWriterThreshold: 32, + pauseWriterThreshold: 64 + )); + + PipeWriter writableBuffer = pipe.Writer.WriteEmpty(64); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + + Assert.False(flushAsync.IsCompleted); + + var id = 0; + + Func doWrite = async () => { + await flushAsync; + + pipe.Writer.Complete(); + + Assert.Equal(Thread.CurrentThread.ManagedThreadId, id); + }; + + Task writing = doWrite(); + + ReadResult result = await pipe.Reader.ReadAsync(); + + id = Thread.CurrentThread.ManagedThreadId; + + pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); + + pipe.Reader.Complete(); + + await writing; + } + } + + [Fact] + public async Task FlushCallbackRunsOnWriterScheduler() + { + using (var pool = new TestMemoryPool()) + { + using (var scheduler = new ThreadScheduler()) + { + var pipe = new Pipe( + new PipeOptions( + pool, + resumeWriterThreshold: 32, + pauseWriterThreshold: 64, + writerScheduler: scheduler)); + + PipeWriter writableBuffer = pipe.Writer.WriteEmpty(64); + PipeAwaiter flushAsync = writableBuffer.FlushAsync(); + + Assert.False(flushAsync.IsCompleted); + + Func doWrite = async () => { + int oid = Thread.CurrentThread.ManagedThreadId; + + await flushAsync; + + Assert.NotEqual(oid, Thread.CurrentThread.ManagedThreadId); + + pipe.Writer.Complete(); + + Assert.Equal(Thread.CurrentThread.ManagedThreadId, scheduler.Thread.ManagedThreadId); + }; + + Task writing = doWrite(); + + ReadResult result = await pipe.Reader.ReadAsync(); + + pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); + + pipe.Reader.Complete(); + + await writing; + } + } + } + + [Fact] + public async Task ReadAsyncCallbackRunsOnReaderScheduler() + { + using (var pool = new TestMemoryPool()) + { + using (var scheduler = new ThreadScheduler()) + { + var pipe = new Pipe(new PipeOptions(pool, scheduler)); + + Func doRead = async () => { + int oid = Thread.CurrentThread.ManagedThreadId; + + ReadResult result = await pipe.Reader.ReadAsync(); + + Assert.NotEqual(oid, Thread.CurrentThread.ManagedThreadId); + + Assert.Equal(Thread.CurrentThread.ManagedThreadId, scheduler.Thread.ManagedThreadId); + + pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); + + pipe.Reader.Complete(); + }; + + Task reading = doRead(); + + PipeWriter buffer = pipe.Writer; + buffer.Write(Encoding.UTF8.GetBytes("Hello World")); + await buffer.FlushAsync(); + + await reading; + } + } + } + } +} diff --git a/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj b/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj index fdc1061d4152..2a7524e707c1 100644 --- a/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj +++ b/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj @@ -7,6 +7,25 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/System.IO.Pipelines/tests/TestMemoryPool.cs b/src/System.IO.Pipelines/tests/TestMemoryPool.cs new file mode 100644 index 000000000000..d33aeac2040d --- /dev/null +++ b/src/System.IO.Pipelines/tests/TestMemoryPool.cs @@ -0,0 +1,139 @@ +// 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.Buffers; +using System.Diagnostics; +using System.Threading; + +namespace System.IO.Pipelines +{ + public class TestMemoryPool: MemoryPool + { + private MemoryPool _pool = Shared; + + private bool _disposed; + + public override OwnedMemory Rent(int minBufferSize = -1) + { + CheckDisposed(); + return new PooledMemory(_pool.Rent(minBufferSize), this); + } + + protected override void Dispose(bool disposing) + { + _disposed = true; + } + + public override int MaxBufferSize => _pool.MaxBufferSize; + + internal void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(TestMemoryPool)); + } + } + + private class PooledMemory : OwnedMemory + { + private OwnedMemory _ownedMemory; + + private readonly TestMemoryPool _pool; + + private int _referenceCount; + + private bool _returned; + + private string _leaser; + + public PooledMemory(OwnedMemory ownedMemory, TestMemoryPool pool) + { + _ownedMemory = ownedMemory; + _pool = pool; + _leaser = Environment.StackTrace; + } + + ~PooledMemory() + { + Debug.Assert(_returned, "Block being garbage collected instead of returned to pool" + Environment.NewLine + _leaser); + } + + protected override void Dispose(bool disposing) + { + _pool.CheckDisposed(); + _ownedMemory.Dispose(); + } + + public override MemoryHandle Pin(int byteOffset = 0) + { + _pool.CheckDisposed(); + return _ownedMemory.Pin(byteOffset); + } + + public override void Retain() + { + _pool.CheckDisposed(); + Interlocked.Increment(ref _referenceCount); + } + + public override bool Release() + { + _pool.CheckDisposed(); + int newRefCount = Interlocked.Decrement(ref _referenceCount); + + if (newRefCount < 0) + throw new InvalidOperationException(); + + if (newRefCount == 0) + { + _returned = true; + return false; + } + return true; + } + + protected override bool TryGetArray(out ArraySegment arraySegment) + { + _pool.CheckDisposed(); + return _ownedMemory.Memory.TryGetArray(out arraySegment); + } + + public override bool IsDisposed + { + get + { + _pool.CheckDisposed(); + return _ownedMemory.IsDisposed; + } + } + + protected override bool IsRetained + { + get + { + _pool.CheckDisposed(); + return !_returned; + } + } + + public override int Length + { + get + { + _pool.CheckDisposed(); + return _ownedMemory.Length; + } + } + + public override Span Span + { + get + { + _pool.CheckDisposed(); + return _ownedMemory.Span; + } + } + } + } +} diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index c6447fbd5b95..cd649742450d 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -216,6 +216,12 @@ public ref partial struct Enumerator } namespace System.Buffers { + public partial interface IBufferWriter + { + void Advance(int bytes); + System.Memory GetMemory(int minimumLength = 0); + System.Span GetSpan(int minimumLength = 0); + } public partial interface IMemoryList { System.Memory Memory { get; } @@ -273,6 +279,7 @@ public static partial class BuffersExtensions public static void CopyTo(this System.Buffers.ReadOnlySequence sequence, System.Span destination) { } public static System.Nullable PositionOf(this System.Buffers.ReadOnlySequence sequence, T value) where T : System.IEquatable { throw null; } public static T[] ToArray(this System.Buffers.ReadOnlySequence sequence) { throw null; } + public static void Write(this System.Buffers.IBufferWriter bufferWriter, ReadOnlySpan source) { } } public readonly partial struct ReadOnlySequence { diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj index 31f7f45b0cf4..b3749679c930 100644 --- a/src/System.Memory/src/System.Memory.csproj +++ b/src/System.Memory/src/System.Memory.csproj @@ -29,7 +29,7 @@ - + diff --git a/src/System.Memory/src/System/Buffers/SequenceExtensions.cs b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs similarity index 66% rename from src/System.Memory/src/System/Buffers/SequenceExtensions.cs rename to src/System.Memory/src/System/Buffers/BuffersExtensions.cs index 4237ff6df985..0822a4760138 100644 --- a/src/System.Memory/src/System/Buffers/SequenceExtensions.cs +++ b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs @@ -54,5 +54,43 @@ public static T[] ToArray(this ReadOnlySequence sequence) sequence.CopyTo(array); return array; } + + /// + /// Writes contents of to + /// + public static void Write(this IBufferWriter bufferWriter, ReadOnlySpan source) + { + Span span = bufferWriter.GetSpan(); + + // Fast path, try copying to the available memory directly + if (source.Length <= span.Length) + { + source.CopyTo(span); + bufferWriter.Advance(source.Length); + return; + } + + var remaining = source.Length; + var offset = 0; + + while (remaining > 0) + { + var writable = Math.Min(remaining, span.Length); + + span = bufferWriter.GetSpan(writable); + + if (writable == 0) + { + continue; + } + + source.Slice(offset, writable).CopyTo(span); + + remaining -= writable; + offset += writable; + + bufferWriter.Advance(writable); + } + } } } diff --git a/src/System.Memory/src/System/Buffers/IBufferWriter.cs b/src/System.Memory/src/System/Buffers/IBufferWriter.cs index 6883deb38019..c84e96551dca 100644 --- a/src/System.Memory/src/System/Buffers/IBufferWriter.cs +++ b/src/System.Memory/src/System/Buffers/IBufferWriter.cs @@ -9,19 +9,19 @@ namespace System.Buffers public interface IBufferWriter { /// - /// Notifies that amount of data was written to / + /// Notifies that amount of data was written to / /// void Advance(int bytes); /// - /// Requests the of at least in size. - /// If is equal to 0, currently available memory would get returned. + /// Requests the of at least in size. + /// If is equal to 0, currently available memory would get returned. /// Memory GetMemory(int minimumLength = 0); /// - /// Requests the of at least in size. - /// If is equal to 0, currently available memory would get returned. + /// Requests the of at least in size. + /// If is equal to 0, currently available memory would get returned. /// Span GetSpan(int minimumLength = 0); } From c3a898a75c6fd0806631e072b4d89825dbf4ddb9 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Fri, 9 Feb 2018 09:48:45 -0800 Subject: [PATCH 03/14] Resources --- .../src/Resources/Strings.resx | 159 ++++++++++++++ .../src/System.IO.Pipelines.csproj | 2 +- .../src/System/IO/Pipelines/Pipe.cs | 23 +- .../src/System/IO/Pipelines/PipeOptions.cs | 11 + .../src/System/IO/Pipelines/PipeWriter.cs | 33 +-- .../IO/Pipelines/SpanLiteralExtensions.cs | 72 ------- .../src/System/IO/Pipelines/ThrowHelper.cs | 47 ++-- .../tests/PipeLengthTests.cs | 204 +++++++++--------- .../tests/PipeResetTests.cs | 3 +- .../tests/PipeWriterTests.cs | 35 ++- 10 files changed, 327 insertions(+), 262 deletions(-) create mode 100644 src/System.IO.Pipelines/src/Resources/Strings.resx delete mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/SpanLiteralExtensions.cs diff --git a/src/System.IO.Pipelines/src/Resources/Strings.resx b/src/System.IO.Pipelines/src/Resources/Strings.resx new file mode 100644 index 000000000000..d923f06339fb --- /dev/null +++ b/src/System.IO.Pipelines/src/Resources/Strings.resx @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Pipe is already advanced past provided cursor. + + + Advancing examined to the end would cause pipe to deadlock because FlushAsync is waiting. + + + Can't advance past buffer size. + + + Can't complete reader while reading. + + + Can't complete writer while writing. + + + Concurrent reads or writes are not supported. + + + Can't GetResult unless awaiter is completed. + + + No reading operation to complete. + + + No writing operation. Make sure GetMemory() was called. + + + Both reader and writer has to be completed to be able to reset the pipe. + + + Reading is not allowed after reader was completed. + + + Reading is already in progress. + + + Writing is not allowed after writer was completed. + + \ No newline at end of file diff --git a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj index 214ef10dc2be..2fe8d336e01e 100644 --- a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj +++ b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj @@ -9,6 +9,7 @@ + @@ -27,7 +28,6 @@ - diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs index d0120a74d75c..5a844e79d87e 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs @@ -80,22 +80,7 @@ public Pipe(PipeOptions options) { if (options == null) { - throw new ArgumentNullException(nameof(options)); - } - - if (options.ResumeWriterThreshold < 0) - { - throw new ArgumentOutOfRangeException(nameof(options.ResumeWriterThreshold)); - } - - if (options.PauseWriterThreshold < 0) - { - throw new ArgumentOutOfRangeException(nameof(options.PauseWriterThreshold)); - } - - if (options.ResumeWriterThreshold > options.PauseWriterThreshold) - { - throw new ArgumentException(nameof(options.PauseWriterThreshold) + " should be greater or equal to " + nameof(options.ResumeWriterThreshold), nameof(options.PauseWriterThreshold)); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.options); } _bufferSegmentPool = new BufferSegment[SegmentPoolSize]; @@ -482,7 +467,7 @@ internal void OnWriterCompleted(Action callback, object state { if (callback == null) { - throw new ArgumentNullException(nameof(callback)); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.callback); } PipeCompletionCallbacks completionCallbacks; @@ -521,7 +506,7 @@ internal void OnReaderCompleted(Action callback, object state { if (callback == null) { - throw new ArgumentNullException(nameof(callback)); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callback); } PipeCompletionCallbacks completionCallbacks; @@ -763,7 +748,7 @@ public void Reset() { if (!_disposed) { - throw new InvalidOperationException("Both reader and writer need to be completed to be able to reset "); + ThrowHelper.ThrowInvalidOperationException_ResetIncompleteReaderWriter(); } _disposed = false; diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs index 7303eebedd70..fb3ac117ded7 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs @@ -27,6 +27,17 @@ public PipeOptions( long resumeWriterThreshold = 0, int minimumSegmentSize = 2048) { + + if (resumeWriterThreshold < 0 && resumeWriterThreshold > pauseWriterThreshold) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.resumeWriterThreshold); + } + + if (pauseWriterThreshold < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.pauseWriterThreshold); + } + Pool = pool ?? MemoryPool.Shared; ReaderScheduler = readerScheduler; WriterScheduler = writerScheduler; diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs index f4709adef574..a04140d010eb 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs @@ -51,38 +51,7 @@ public abstract class PipeWriter : IBufferWriter /// public virtual PipeAwaiter WriteAsync(ReadOnlyMemory source) { - Span destination = GetSpan(); - var sourceSpan = source.Span; - // Fast path, try copying to the available memory directly - if (sourceSpan.Length <= destination.Length) - { - sourceSpan.CopyTo(destination); - Advance(sourceSpan.Length); - return FlushAsync(); - } - - var remaining = sourceSpan.Length; - var offset = 0; - - while (remaining > 0) - { - var writable = Math.Min(remaining, destination.Length); - - destination = GetSpan(writable); - - if (writable == 0) - { - continue; - } - - sourceSpan.Slice(offset, writable).CopyTo(destination); - - remaining -= writable; - offset += writable; - - Advance(writable); - } - + this.Write(source.Span); return FlushAsync(); } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/SpanLiteralExtensions.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/SpanLiteralExtensions.cs deleted file mode 100644 index 128283b7d821..000000000000 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/SpanLiteralExtensions.cs +++ /dev/null @@ -1,72 +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.Text; - -namespace System.IO.Pipelines -{ - internal class SpanLiteralExtensions - { - internal static void AppendAsLiteral(ReadOnlySpan span, StringBuilder sb) - { - for (int i = 0; i < span.Length; i++) - { - AppendCharLiteral((char) span[i], sb); - } - } - - internal static void AppendCharLiteral(char c, StringBuilder sb) - { - switch (c) - { - case '\'': - sb.Append(@"\'"); - break; - case '\"': - sb.Append("\\\""); - break; - case '\\': - sb.Append(@"\\"); - break; - case '\0': - sb.Append(@"\0"); - break; - case '\a': - sb.Append(@"\a"); - break; - case '\b': - sb.Append(@"\b"); - break; - case '\f': - sb.Append(@"\f"); - break; - case '\n': - sb.Append(@"\n"); - break; - case '\r': - sb.Append(@"\r"); - break; - case '\t': - sb.Append(@"\t"); - break; - case '\v': - sb.Append(@"\v"); - break; - default: - // ASCII printable character - if (!char.IsControl(c)) - { - sb.Append(c); - // As UTF16 escaped character - } - else - { - sb.Append(@"\u"); - sb.Append(((int) c).ToString("x4")); - } - break; - } - } - } -} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs index 475dd13348df..a33fd8fe3c1f 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs @@ -12,6 +12,10 @@ internal class ThrowHelper [MethodImpl(MethodImplOptions.NoInlining)] private static Exception CreateArgumentOutOfRangeException(ExceptionArgument argument) { return new ArgumentOutOfRangeException(argument.ToString()); } + internal static void ThrowArgumentNullException(ExceptionArgument argument) { throw CreateArgumentNullException(argument); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static Exception CreateArgumentNullException(ExceptionArgument argument) { return new ArgumentOutOfRangeException(argument.ToString()); } + public static void ThrowInvalidOperationException_NotWritingNoAlloc() { throw CreateInvalidOperationException_NotWritingNoAlloc(); @@ -20,7 +24,7 @@ public static void ThrowInvalidOperationException_NotWritingNoAlloc() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_NotWritingNoAlloc() { - return new InvalidOperationException("No writing operation. Make sure GetMemory() was called."); + return new InvalidOperationException(SR.NoWritingOperation); } public static void ThrowInvalidOperationException_AlreadyReading() @@ -31,7 +35,7 @@ public static void ThrowInvalidOperationException_AlreadyReading() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_AlreadyReading() { - return new InvalidOperationException("Already reading."); + return new InvalidOperationException(SR.ReadingIsInProgress); } public static void ThrowInvalidOperationException_NoReadToComplete() @@ -42,7 +46,7 @@ public static void ThrowInvalidOperationException_NoReadToComplete() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_NoReadToComplete() { - return new InvalidOperationException("No reading operation to complete."); + return new InvalidOperationException(SR.NoReadingOperationToComplete); } public static void ThrowInvalidOperationException_NoConcurrentOperation() @@ -53,7 +57,7 @@ public static void ThrowInvalidOperationException_NoConcurrentOperation() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_NoConcurrentOperation() { - return new InvalidOperationException("Concurrent reads or writes are not supported."); + return new InvalidOperationException(SR.ConcurrentOperationsNotSupported); } public static void ThrowInvalidOperationException_GetResultNotCompleted() @@ -64,7 +68,7 @@ public static void ThrowInvalidOperationException_GetResultNotCompleted() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_GetResultNotCompleted() { - return new InvalidOperationException("Can't GetResult unless completed"); + return new InvalidOperationException(SR.GetResultBeforeCompleted); } public static void ThrowInvalidOperationException_NoWritingAllowed() @@ -75,7 +79,7 @@ public static void ThrowInvalidOperationException_NoWritingAllowed() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_NoWritingAllowed() { - return new InvalidOperationException("Writing is not allowed after writer was completed"); + return new InvalidOperationException(SR.ReadingAfterCompleted); } public static void ThrowInvalidOperationException_NoReadingAllowed() @@ -87,7 +91,7 @@ public static void ThrowInvalidOperationException_NoReadingAllowed() public static Exception CreateInvalidOperationException_NoReadingAllowed() { - return new InvalidOperationException("Reading is not allowed after reader was completed"); + return new InvalidOperationException(SR.WritingAfterCompleted); } public static void ThrowInvalidOperationException_CompleteWriterActiveWriter() @@ -98,7 +102,7 @@ public static void ThrowInvalidOperationException_CompleteWriterActiveWriter() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_CompleteWriterActiveWriter() { - return new InvalidOperationException("Can't complete writer while writing."); + return new InvalidOperationException(SR.CannotCompleteWhiteWriting); } public static void ThrowInvalidOperationException_CompleteReaderActiveReader() @@ -109,7 +113,7 @@ public static void ThrowInvalidOperationException_CompleteReaderActiveReader() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_CompleteReaderActiveReader() { - return new InvalidOperationException("Can't complete reader while reading."); + return new InvalidOperationException(SR.CannotCompleteWhileReading); } public static void ThrowInvalidOperationException_AdvancingPastBufferSize() @@ -120,7 +124,7 @@ public static void ThrowInvalidOperationException_AdvancingPastBufferSize() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_AdvancingPastBufferSize() { - return new InvalidOperationException("Can't advance past buffer size"); + return new InvalidOperationException(SR.CannotAdvancePastCurrentBufferSize); } public static void ThrowInvalidOperationException_BackpressureDeadlock() @@ -131,7 +135,7 @@ public static void ThrowInvalidOperationException_BackpressureDeadlock() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_BackpressureDeadlock() { - return new InvalidOperationException("Advancing examined to the end would cause pipe to deadlock because FlushAsync is waiting"); + return new InvalidOperationException(SR.BackpressureDeadlock); } public static void ThrowInvalidOperationException_AdvanceToInvalidCursor() @@ -142,13 +146,30 @@ public static void ThrowInvalidOperationException_AdvanceToInvalidCursor() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_AdvanceToInvalidCursor() { - return new InvalidOperationException("Pipe is already advanced past provided cursor"); + return new InvalidOperationException(SR.AdvanceToInvalidCursor); + } + + public static void ThrowInvalidOperationException_ResetIncompleteReaderWriter() + { + throw CreateInvalidOperationException_ResetIncompleteReaderWriter(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_ResetIncompleteReaderWriter() + { + return new InvalidOperationException(SR.AdvanceToInvalidCursor); } } internal enum ExceptionArgument { minimumSize, - bytesWritten + bytesWritten, + callback, + options, + + pauseWriterThreshold, + + resumeWriterThreshold } } diff --git a/src/System.IO.Pipelines/tests/PipeLengthTests.cs b/src/System.IO.Pipelines/tests/PipeLengthTests.cs index fe413dae817e..9901da9d5583 100644 --- a/src/System.IO.Pipelines/tests/PipeLengthTests.cs +++ b/src/System.IO.Pipelines/tests/PipeLengthTests.cs @@ -1,102 +1,102 @@ -// 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 Xunit; - -namespace System.IO.Pipelines.Tests -{ - public class PipeLengthTests : IDisposable - { - public PipeLengthTests() - { - _pool = new TestMemoryPool(); - _pipe = new Pipe(new PipeOptions(_pool)); - } - - public void Dispose() - { - _pipe.Writer.Complete(); - _pipe.Reader.Complete(); - _pool?.Dispose(); - } - - private readonly TestMemoryPool _pool; - - private readonly Pipe _pipe; - - [Fact] - public void ByteByByteTest() - { - for (var i = 1; i <= 1024 * 1024; i++) - { - _pipe.Writer.GetMemory(100); - _pipe.Writer.Advance(1); - _pipe.Writer.Commit(); - - Assert.Equal(i, _pipe.Length); - } - - _pipe.Writer.FlushAsync(); - - for (int i = 1024 * 1024 - 1; i >= 0; i--) - { - ReadResult result = _pipe.Reader.ReadAsync().GetResult(); - SequencePosition consumed = result.Buffer.Slice(1).Start; - - Assert.Equal(i + 1, result.Buffer.Length); - - _pipe.Reader.AdvanceTo(consumed, consumed); - - Assert.Equal(i, _pipe.Length); - } - } - - [Fact] - public void LengthCorrectAfterAlloc0AdvanceCommit() - { - _pipe.Writer.GetMemory(0); - _pipe.Writer.WriteEmpty(10); - _pipe.Writer.Commit(); - - Assert.Equal(10, _pipe.Length); - } - - [Fact] - public void LengthCorrectAfterAllocAdvanceCommit() - { - PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); - writableBuffer.Commit(); - - Assert.Equal(10, _pipe.Length); - } - - [Fact] - public void LengthDecreasedAfterReadAdvanceConsume() - { - _pipe.Writer.GetMemory(100); - _pipe.Writer.Advance(10); - _pipe.Writer.Commit(); - _pipe.Writer.FlushAsync(); - - ReadResult result = _pipe.Reader.ReadAsync().GetResult(); - SequencePosition consumed = result.Buffer.Slice(5).Start; - _pipe.Reader.AdvanceTo(consumed, consumed); - - Assert.Equal(5, _pipe.Length); - } - - [Fact] - public void LengthNotChangeAfterReadAdvanceExamine() - { - PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); - writableBuffer.Commit(); - writableBuffer.FlushAsync(); - - ReadResult result = _pipe.Reader.ReadAsync().GetResult(); - _pipe.Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End); - - Assert.Equal(10, _pipe.Length); - } - } -} +//// 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 Xunit; + +//namespace System.IO.Pipelines.Tests +//{ +// public class PipeLengthTests : IDisposable +// { +// public PipeLengthTests() +// { +// _pool = new TestMemoryPool(); +// _pipe = new Pipe(new PipeOptions(_pool)); +// } + +// public void Dispose() +// { +// _pipe.Writer.Complete(); +// _pipe.Reader.Complete(); +// _pool?.Dispose(); +// } + +// private readonly TestMemoryPool _pool; + +// private readonly Pipe _pipe; + +// [Fact] +// public void ByteByByteTest() +// { +// for (var i = 1; i <= 1024 * 1024; i++) +// { +// _pipe.Writer.GetMemory(100); +// _pipe.Writer.Advance(1); +// _pipe.Writer.Commit(); + +// Assert.Equal(i, _pipe.Length); +// } + +// _pipe.Writer.FlushAsync(); + +// for (int i = 1024 * 1024 - 1; i >= 0; i--) +// { +// ReadResult result = _pipe.Reader.ReadAsync().GetResult(); +// SequencePosition consumed = result.Buffer.Slice(1).Start; + +// Assert.Equal(i + 1, result.Buffer.Length); + +// _pipe.Reader.AdvanceTo(consumed, consumed); + +// Assert.Equal(i, _pipe.Length); +// } +// } + +// [Fact] +// public void LengthCorrectAfterAlloc0AdvanceCommit() +// { +// _pipe.Writer.GetMemory(0); +// _pipe.Writer.WriteEmpty(10); +// _pipe.Writer.Commit(); + +// Assert.Equal(10, _pipe.Length); +// } + +// [Fact] +// public void LengthCorrectAfterAllocAdvanceCommit() +// { +// PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); +// writableBuffer.Commit(); + +// Assert.Equal(10, _pipe.Length); +// } + +// [Fact] +// public void LengthDecreasedAfterReadAdvanceConsume() +// { +// _pipe.Writer.GetMemory(100); +// _pipe.Writer.Advance(10); +// _pipe.Writer.Commit(); +// _pipe.Writer.FlushAsync(); + +// ReadResult result = _pipe.Reader.ReadAsync().GetResult(); +// SequencePosition consumed = result.Buffer.Slice(5).Start; +// _pipe.Reader.AdvanceTo(consumed, consumed); + +// Assert.Equal(5, _pipe.Length); +// } + +// [Fact] +// public void LengthNotChangeAfterReadAdvanceExamine() +// { +// PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); +// writableBuffer.Commit(); +// writableBuffer.FlushAsync(); + +// ReadResult result = _pipe.Reader.ReadAsync().GetResult(); +// _pipe.Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End); + +// Assert.Equal(10, _pipe.Length); +// } +// } +//} diff --git a/src/System.IO.Pipelines/tests/PipeResetTests.cs b/src/System.IO.Pipelines/tests/PipeResetTests.cs index d0582855e7df..83c0019ca29c 100644 --- a/src/System.IO.Pipelines/tests/PipeResetTests.cs +++ b/src/System.IO.Pipelines/tests/PipeResetTests.cs @@ -2,6 +2,7 @@ // 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.Buffers; using System.Threading.Tasks; using Xunit; @@ -38,7 +39,7 @@ public async Task LengthIsReseted() _pipe.Reset(); - Assert.Equal(0, _pipe.Length); + //Assert.Equal(0, _pipe.Length); } [Fact] diff --git a/src/System.IO.Pipelines/tests/PipeWriterTests.cs b/src/System.IO.Pipelines/tests/PipeWriterTests.cs index 5f3ea2336d7c..825c7ddce450 100644 --- a/src/System.IO.Pipelines/tests/PipeWriterTests.cs +++ b/src/System.IO.Pipelines/tests/PipeWriterTests.cs @@ -33,7 +33,7 @@ private byte[] Read() [InlineData(3, 4, 4)] public void ThrowsForInvalidParameters(int arrayLength, int offset, int length) { - OutputWriter writer = OutputWriter.Create(Pipe.Writer); + PipeWriter writer = Pipe.Writer; var array = new byte[arrayLength]; for (var i = 0; i < array.Length; i++) { @@ -68,7 +68,7 @@ public void ThrowsForInvalidParameters(int arrayLength, int offset, int length) [InlineData(1, 1, 1)] public void CanWriteWithOffsetAndLenght(int alloc, int offset, int length) { - OutputWriter writer = OutputWriter.Create(Pipe.Writer); + PipeWriter writer = Pipe.Writer; var array = new byte[] { 1, 2, 3 }; writer.Write(new Span(array, offset, length)); @@ -79,7 +79,7 @@ public void CanWriteWithOffsetAndLenght(int alloc, int offset, int length) [Fact] public void CanWriteEmpty() { - OutputWriter writer = OutputWriter.Create(Pipe.Writer); + PipeWriter writer = Pipe.Writer; var array = new byte[] { }; writer.Write(array); @@ -91,7 +91,7 @@ public void CanWriteEmpty() [Fact] public void CanWriteIntoHeadlessBuffer() { - OutputWriter writer = OutputWriter.Create(Pipe.Writer); + PipeWriter writer = Pipe.Writer; writer.Write(new byte[] { 1, 2, 3 }); Assert.Equal(new byte[] { 1, 2, 3 }, Read()); @@ -100,7 +100,7 @@ public void CanWriteIntoHeadlessBuffer() [Fact] public void CanWriteMultipleTimes() { - OutputWriter writer = OutputWriter.Create(Pipe.Writer); + PipeWriter writer = Pipe.Writer; writer.Write(new byte[] { 1 }); writer.Write(new byte[] { 2 }); @@ -113,7 +113,7 @@ public void CanWriteMultipleTimes() public void CanWriteOverTheBlockLength() { Memory memory = Pipe.Writer.GetMemory(); - OutputWriter writer = OutputWriter.Create(Pipe.Writer); + PipeWriter writer = Pipe.Writer; IEnumerable source = Enumerable.Range(0, memory.Length).Select(i => (byte)i); byte[] expectedBytes = source.Concat(source).Concat(source).ToArray(); @@ -126,33 +126,24 @@ public void CanWriteOverTheBlockLength() [Fact] public void EnsureAllocatesSpan() { - OutputWriter writer = OutputWriter.Create(Pipe.Writer); - writer.Ensure(10); + PipeWriter writer = Pipe.Writer; + var span = writer.GetSpan(10); - Assert.True(writer.Span.Length > 10); - Assert.Equal(new byte[] { }, Read()); - } - - [Fact] - public void ExposesSpan() - { - int initialLength = Pipe.Writer.GetMemory().Length; - OutputWriter writer = OutputWriter.Create(Pipe.Writer); - Assert.Equal(initialLength, writer.Span.Length); + Assert.True(span.Length >= 10); Assert.Equal(new byte[] { }, Read()); } [Fact] public void SlicesSpanAndAdvancesAfterWrite() { - int initialLength = Pipe.Writer.GetMemory().Length; + int initialLength = Pipe.Writer.GetSpan(3).Length; - OutputWriter writer = OutputWriter.Create(Pipe.Writer); + PipeWriter writer = Pipe.Writer; writer.Write(new byte[] { 1, 2, 3 }); + Span span = Pipe.Writer.GetSpan(); - Assert.Equal(initialLength - 3, writer.Span.Length); - Assert.Equal(Pipe.Writer.GetMemory().Length, writer.Span.Length); + Assert.Equal(initialLength - 3, span.Length); Assert.Equal(new byte[] { 1, 2, 3 }, Read()); } From 4acf7f927ec0c132b301c79d79a6f3e5ba7d69fa Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Fri, 9 Feb 2018 11:00:30 -0800 Subject: [PATCH 04/14] Cover --- .../src/Properties/InternalsVisibleTo.cs | 9 + .../src/System/IO/Pipelines/Pipe.cs | 6 +- .../src/System/IO/Pipelines/PipeAwaitable.cs | 7 +- .../src/System/IO/Pipelines/PipeCompletion.cs | 1 + .../System/IO/Pipelines/PipeReaderState.cs | 9 +- .../src/System/IO/Pipelines/ThrowHelper.cs | 4 +- .../tests/PipeLengthTests.cs | 204 +++++++++--------- ...riterFacts.cs => PipeReaderWriterFacts.cs} | 51 +++++ .../tests/PipeResetTests.cs | 2 +- .../tests/PipeWriterTests.cs | 2 +- .../tests/SchedulerFacts.cs | 67 ++++-- .../tests/System.IO.Pipelines.Tests.csproj | 7 +- 12 files changed, 230 insertions(+), 139 deletions(-) create mode 100644 src/System.IO.Pipelines/src/Properties/InternalsVisibleTo.cs rename src/System.IO.Pipelines/tests/{PipelineReaderWriterFacts.cs => PipeReaderWriterFacts.cs} (91%) diff --git a/src/System.IO.Pipelines/src/Properties/InternalsVisibleTo.cs b/src/System.IO.Pipelines/src/Properties/InternalsVisibleTo.cs new file mode 100644 index 000000000000..76ab3f9ae612 --- /dev/null +++ b/src/System.IO.Pipelines/src/Properties/InternalsVisibleTo.cs @@ -0,0 +1,9 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("System.IO.Pipelines.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs index 5a844e79d87e..7cf3ad719472 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs @@ -419,14 +419,14 @@ internal void AdvanceReader(SequencePosition consumed, SequencePosition examined _readerAwaitable.Reset(); } - _readingState.End(); - while (returnStart != null && returnStart != returnEnd) { returnStart.ResetMemory(); ReturnSegmentUnsynchronized(returnStart); returnStart = returnStart.NextSegment; } + + _readingState.End(); } TrySchedule(_writerScheduler, continuation); @@ -467,7 +467,7 @@ internal void OnWriterCompleted(Action callback, object state { if (callback == null) { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.callback); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callback); } PipeCompletionCallbacks completionCallbacks; diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs index b0fcb397e8e6..3bdcacbd7285 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs @@ -2,11 +2,13 @@ // 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.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; namespace System.IO.Pipelines { + [DebuggerDisplay("CancelledState: {_canceledState}, IsCompleted: {IsCompleted}")] internal struct PipeAwaitable { private static readonly Action _awaitableIsCompleted = () => { }; @@ -134,11 +136,6 @@ public bool ObserveCancelation() return false; } - public override string ToString() - { - return $"CancelledState: {_canceledState}, {nameof(IsCompleted)}: {IsCompleted}"; - } - private enum CanceledState { NotCanceled = 0, diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs index 321554bb765b..6e9600be95df 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs @@ -9,6 +9,7 @@ namespace System.IO.Pipelines { + [DebuggerDisplay("IsCompleted: {" + nameof(IsCompleted) + "}")] internal struct PipeCompletion { private static readonly ArrayPool CompletionCallbackPool = ArrayPool.Shared; diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs index 2a8e195be70c..223a1e58a387 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs @@ -1,10 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Diagnostics; using System.Runtime.CompilerServices; namespace System.IO.Pipelines { + [DebuggerDisplay("State: {_state}")] internal struct PipeReaderState { private State _state; @@ -38,18 +40,13 @@ public void End() { if (_state == State.Inactive) { - ThrowHelper.CreateInvalidOperationException_NoReadToComplete(); + ThrowHelper.ThrowInvalidOperationException_NoReadToComplete(); } _state = State.Inactive; } public bool IsActive => _state == State.Active; - - public override string ToString() - { - return $"State: {_state}"; - } } internal enum State: byte diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs index a33fd8fe3c1f..fa43618d45b8 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs @@ -14,7 +14,7 @@ internal class ThrowHelper internal static void ThrowArgumentNullException(ExceptionArgument argument) { throw CreateArgumentNullException(argument); } [MethodImpl(MethodImplOptions.NoInlining)] - private static Exception CreateArgumentNullException(ExceptionArgument argument) { return new ArgumentOutOfRangeException(argument.ToString()); } + private static Exception CreateArgumentNullException(ExceptionArgument argument) { return new ArgumentNullException(argument.ToString()); } public static void ThrowInvalidOperationException_NotWritingNoAlloc() { @@ -157,7 +157,7 @@ public static void ThrowInvalidOperationException_ResetIncompleteReaderWriter() [MethodImpl(MethodImplOptions.NoInlining)] public static Exception CreateInvalidOperationException_ResetIncompleteReaderWriter() { - return new InvalidOperationException(SR.AdvanceToInvalidCursor); + return new InvalidOperationException(SR.ReaderAndWriterHasToBeCompleted); } } diff --git a/src/System.IO.Pipelines/tests/PipeLengthTests.cs b/src/System.IO.Pipelines/tests/PipeLengthTests.cs index 9901da9d5583..fe413dae817e 100644 --- a/src/System.IO.Pipelines/tests/PipeLengthTests.cs +++ b/src/System.IO.Pipelines/tests/PipeLengthTests.cs @@ -1,102 +1,102 @@ -//// 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 Xunit; - -//namespace System.IO.Pipelines.Tests -//{ -// public class PipeLengthTests : IDisposable -// { -// public PipeLengthTests() -// { -// _pool = new TestMemoryPool(); -// _pipe = new Pipe(new PipeOptions(_pool)); -// } - -// public void Dispose() -// { -// _pipe.Writer.Complete(); -// _pipe.Reader.Complete(); -// _pool?.Dispose(); -// } - -// private readonly TestMemoryPool _pool; - -// private readonly Pipe _pipe; - -// [Fact] -// public void ByteByByteTest() -// { -// for (var i = 1; i <= 1024 * 1024; i++) -// { -// _pipe.Writer.GetMemory(100); -// _pipe.Writer.Advance(1); -// _pipe.Writer.Commit(); - -// Assert.Equal(i, _pipe.Length); -// } - -// _pipe.Writer.FlushAsync(); - -// for (int i = 1024 * 1024 - 1; i >= 0; i--) -// { -// ReadResult result = _pipe.Reader.ReadAsync().GetResult(); -// SequencePosition consumed = result.Buffer.Slice(1).Start; - -// Assert.Equal(i + 1, result.Buffer.Length); - -// _pipe.Reader.AdvanceTo(consumed, consumed); - -// Assert.Equal(i, _pipe.Length); -// } -// } - -// [Fact] -// public void LengthCorrectAfterAlloc0AdvanceCommit() -// { -// _pipe.Writer.GetMemory(0); -// _pipe.Writer.WriteEmpty(10); -// _pipe.Writer.Commit(); - -// Assert.Equal(10, _pipe.Length); -// } - -// [Fact] -// public void LengthCorrectAfterAllocAdvanceCommit() -// { -// PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); -// writableBuffer.Commit(); - -// Assert.Equal(10, _pipe.Length); -// } - -// [Fact] -// public void LengthDecreasedAfterReadAdvanceConsume() -// { -// _pipe.Writer.GetMemory(100); -// _pipe.Writer.Advance(10); -// _pipe.Writer.Commit(); -// _pipe.Writer.FlushAsync(); - -// ReadResult result = _pipe.Reader.ReadAsync().GetResult(); -// SequencePosition consumed = result.Buffer.Slice(5).Start; -// _pipe.Reader.AdvanceTo(consumed, consumed); - -// Assert.Equal(5, _pipe.Length); -// } - -// [Fact] -// public void LengthNotChangeAfterReadAdvanceExamine() -// { -// PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); -// writableBuffer.Commit(); -// writableBuffer.FlushAsync(); - -// ReadResult result = _pipe.Reader.ReadAsync().GetResult(); -// _pipe.Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End); - -// Assert.Equal(10, _pipe.Length); -// } -// } -//} +// 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 Xunit; + +namespace System.IO.Pipelines.Tests +{ + public class PipeLengthTests : IDisposable + { + public PipeLengthTests() + { + _pool = new TestMemoryPool(); + _pipe = new Pipe(new PipeOptions(_pool)); + } + + public void Dispose() + { + _pipe.Writer.Complete(); + _pipe.Reader.Complete(); + _pool?.Dispose(); + } + + private readonly TestMemoryPool _pool; + + private readonly Pipe _pipe; + + [Fact] + public void ByteByByteTest() + { + for (var i = 1; i <= 1024 * 1024; i++) + { + _pipe.Writer.GetMemory(100); + _pipe.Writer.Advance(1); + _pipe.Writer.Commit(); + + Assert.Equal(i, _pipe.Length); + } + + _pipe.Writer.FlushAsync(); + + for (int i = 1024 * 1024 - 1; i >= 0; i--) + { + ReadResult result = _pipe.Reader.ReadAsync().GetResult(); + SequencePosition consumed = result.Buffer.Slice(1).Start; + + Assert.Equal(i + 1, result.Buffer.Length); + + _pipe.Reader.AdvanceTo(consumed, consumed); + + Assert.Equal(i, _pipe.Length); + } + } + + [Fact] + public void LengthCorrectAfterAlloc0AdvanceCommit() + { + _pipe.Writer.GetMemory(0); + _pipe.Writer.WriteEmpty(10); + _pipe.Writer.Commit(); + + Assert.Equal(10, _pipe.Length); + } + + [Fact] + public void LengthCorrectAfterAllocAdvanceCommit() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); + writableBuffer.Commit(); + + Assert.Equal(10, _pipe.Length); + } + + [Fact] + public void LengthDecreasedAfterReadAdvanceConsume() + { + _pipe.Writer.GetMemory(100); + _pipe.Writer.Advance(10); + _pipe.Writer.Commit(); + _pipe.Writer.FlushAsync(); + + ReadResult result = _pipe.Reader.ReadAsync().GetResult(); + SequencePosition consumed = result.Buffer.Slice(5).Start; + _pipe.Reader.AdvanceTo(consumed, consumed); + + Assert.Equal(5, _pipe.Length); + } + + [Fact] + public void LengthNotChangeAfterReadAdvanceExamine() + { + PipeWriter writableBuffer = _pipe.Writer.WriteEmpty(10); + writableBuffer.Commit(); + writableBuffer.FlushAsync(); + + ReadResult result = _pipe.Reader.ReadAsync().GetResult(); + _pipe.Reader.AdvanceTo(result.Buffer.Start, result.Buffer.End); + + Assert.Equal(10, _pipe.Length); + } + } +} diff --git a/src/System.IO.Pipelines/tests/PipelineReaderWriterFacts.cs b/src/System.IO.Pipelines/tests/PipeReaderWriterFacts.cs similarity index 91% rename from src/System.IO.Pipelines/tests/PipelineReaderWriterFacts.cs rename to src/System.IO.Pipelines/tests/PipeReaderWriterFacts.cs index e069e856af7c..88603a7519bd 100644 --- a/src/System.IO.Pipelines/tests/PipelineReaderWriterFacts.cs +++ b/src/System.IO.Pipelines/tests/PipeReaderWriterFacts.cs @@ -557,5 +557,56 @@ public async Task WritingDataMakesDataReadableViaPipeline() _pipe.Reader.AdvanceTo(buffer.Start, buffer.Start); } + + [Fact] + public async Task DoubleReadThrows() + { + await _pipe.Writer.WriteAsync(new byte[1]); + PipeAwaiter awaiter = _pipe.Reader.ReadAsync(); + ReadResult result = awaiter.GetAwaiter().GetResult(); + + Assert.Throws(() => awaiter.GetAwaiter().GetResult()); + + _pipe.Reader.AdvanceTo(result.Buffer.Start, result.Buffer.Start); + } + + [Fact] + public void GetResultBeforeCompletedThrows() + { + PipeAwaiter awaiter = _pipe.Reader.ReadAsync(); + + Assert.Throws(() => awaiter.GetAwaiter().GetResult()); + } + + [Fact] + public void CompleteAfterAdvanceThrows() + { + _pipe.Writer.WriteEmpty(4); + + Assert.Throws(() => _pipe.Writer.Complete()); + + _pipe.Commit(); + } + + [Fact] + public async Task AdvanceWithoutReadThrows() + { + await _pipe.Writer.WriteAsync(new byte[3]); + ReadResult readResult = await _pipe.Reader.ReadAsync(); + _pipe.Reader.AdvanceTo(readResult.Buffer.Start); + + InvalidOperationException exception = Assert.Throws(() => _pipe.Reader.AdvanceTo(readResult.Buffer.End)); + Assert.Equal("No reading operation to complete.", exception.Message); + } + + [Fact] + public async Task TryReadAfterReadAsyncThrows() + { + await _pipe.Writer.WriteAsync(new byte[3]); + ReadResult readResult = await _pipe.Reader.ReadAsync(); + + Assert.Throws(() => _pipe.Reader.TryRead(out _)); + _pipe.Reader.AdvanceTo(readResult.Buffer.Start); + } } } diff --git a/src/System.IO.Pipelines/tests/PipeResetTests.cs b/src/System.IO.Pipelines/tests/PipeResetTests.cs index 83c0019ca29c..40659e3c6f91 100644 --- a/src/System.IO.Pipelines/tests/PipeResetTests.cs +++ b/src/System.IO.Pipelines/tests/PipeResetTests.cs @@ -39,7 +39,7 @@ public async Task LengthIsReseted() _pipe.Reset(); - //Assert.Equal(0, _pipe.Length); + Assert.Equal(0, _pipe.Length); } [Fact] diff --git a/src/System.IO.Pipelines/tests/PipeWriterTests.cs b/src/System.IO.Pipelines/tests/PipeWriterTests.cs index 825c7ddce450..edad163479ad 100644 --- a/src/System.IO.Pipelines/tests/PipeWriterTests.cs +++ b/src/System.IO.Pipelines/tests/PipeWriterTests.cs @@ -187,7 +187,7 @@ public void ThrowsOnAdvanceOverMemorySize() { Memory buffer = Pipe.Writer.GetMemory(1); var exception = Assert.Throws(() => Pipe.Writer.Advance(buffer.Length + 1)); - Assert.Equal("Can't advance past buffer size", exception.Message); + Assert.Equal("Can't advance past buffer size.", exception.Message); } [Fact] diff --git a/src/System.IO.Pipelines/tests/SchedulerFacts.cs b/src/System.IO.Pipelines/tests/SchedulerFacts.cs index c3bd3f12e9b1..6cc350742913 100644 --- a/src/System.IO.Pipelines/tests/SchedulerFacts.cs +++ b/src/System.IO.Pipelines/tests/SchedulerFacts.cs @@ -52,34 +52,31 @@ private void Work(object state) [Fact] public async Task DefaultReaderSchedulerRunsInline() { - using (var pool = new TestMemoryPool()) - { - var pipe = new Pipe(new PipeOptions(pool)); + var pipe = new Pipe(); - var id = 0; + var id = 0; - Func doRead = async () => { - ReadResult result = await pipe.Reader.ReadAsync(); + Func doRead = async () => { + ReadResult result = await pipe.Reader.ReadAsync(); - Assert.Equal(Thread.CurrentThread.ManagedThreadId, id); + Assert.Equal(Thread.CurrentThread.ManagedThreadId, id); - pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); + pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); - pipe.Reader.Complete(); - }; + pipe.Reader.Complete(); + }; - Task reading = doRead(); + Task reading = doRead(); - id = Thread.CurrentThread.ManagedThreadId; + id = Thread.CurrentThread.ManagedThreadId; - PipeWriter buffer = pipe.Writer; - buffer.Write(Encoding.UTF8.GetBytes("Hello World")); - await buffer.FlushAsync(); + PipeWriter buffer = pipe.Writer; + buffer.Write(Encoding.UTF8.GetBytes("Hello World")); + await buffer.FlushAsync(); - pipe.Writer.Complete(); + pipe.Writer.Complete(); - await reading; - } + await reading; } [Fact] @@ -200,5 +197,39 @@ public async Task ReadAsyncCallbackRunsOnReaderScheduler() } } } + + [Fact] + public async Task ThreadPoolScheduler_SchedulesOnThreadPool() + { + var pipe = new Pipe(new PipeOptions(readerScheduler: PipeScheduler.ThreadPool)); + + async Task DoRead() + { + int oid = Thread.CurrentThread.ManagedThreadId; + + ReadResult result = await pipe.Reader.ReadAsync(); + + Assert.NotEqual(oid, Thread.CurrentThread.ManagedThreadId); + Assert.True(Thread.CurrentThread.IsThreadPoolThread); + pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); + pipe.Reader.Complete(); + } + + bool callbackRan = false; + Task reading = DoRead(); + + PipeWriter buffer = pipe.Writer; + pipe.Writer.OnReaderCompleted((state, exception) => + { + callbackRan = true; + Assert.True(Thread.CurrentThread.IsThreadPoolThread); + }, null); + buffer.Write(Encoding.UTF8.GetBytes("Hello World")); + await buffer.FlushAsync(); + + await reading; + + Assert.True(callbackRan); + } } } diff --git a/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj b/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj index 2a7524e707c1..ffbfb1305576 100644 --- a/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj +++ b/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj @@ -13,7 +13,7 @@ - + @@ -27,5 +27,10 @@ + + + + + \ No newline at end of file From 50713cf859900e8116986e80b6d1a1da736645fc Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 12 Feb 2018 12:35:48 -0800 Subject: [PATCH 05/14] Pipelines RP comments --- .../System.IO.Pipelines.sln | 8 +- .../pkg/System.IO.Pipelines.pkgproj | 3 +- .../ref/Configurations.props | 3 +- .../ref/System.IO.Pipelines.cs | 2 +- .../ref/System.IO.Pipelines.csproj | 5 - .../src/Configurations.props | 2 + .../src/Properties/InternalsVisibleTo.cs | 2 - .../src/System.IO.Pipelines.csproj | 21 ++- .../src/System/IO/Pipelines/BufferSegment.cs | 2 +- .../IO/Pipelines/Pipe.DefaultPipeReader.cs | 44 +++++ .../IO/Pipelines/Pipe.DefaultPipeWriter.cs | 46 +++++ .../src/System/IO/Pipelines/Pipe.cs | 162 +++--------------- .../src/System/IO/Pipelines/PipeAwaitable.cs | 8 +- .../src/System/IO/Pipelines/PipeCompletion.cs | 22 +-- .../IO/Pipelines/PipeCompletionCallbacks.cs | 4 +- .../src/System/IO/Pipelines/PipeOptions.cs | 9 +- .../System/IO/Pipelines/PipeReaderState.cs | 17 +- .../src/System/IO/Pipelines/PipeScheduler.cs | 8 +- .../src/System/IO/Pipelines/PipeWriter.cs | 9 +- .../src/System/IO/Pipelines/ReadResult.cs | 5 +- .../src/System/IO/Pipelines/ResultFlags.cs | 11 +- ...cs => ThreadPoolScheduler.NetCoreApp21.cs} | 14 -- .../ThreadPoolScheduler.NetStandard.cs | 22 +++ .../ThreadPoolScheduler.NetStandard20.cs | 40 +++++ .../src/System/IO/Pipelines/ThrowHelper.cs | 155 ++++------------- .../tests/FlushAsyncCancellationTests.cs | 12 +- .../tests/PipeReaderWriterFacts.cs | 10 +- .../tests/ReadAsyncCancellationTests.cs | 12 +- 28 files changed, 304 insertions(+), 354 deletions(-) create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeReader.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeWriter.cs rename src/System.IO.Pipelines/src/System/IO/Pipelines/{ThreadPoolScheduler.cs => ThreadPoolScheduler.NetCoreApp21.cs} (77%) create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetStandard.cs create mode 100644 src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetStandard20.cs diff --git a/src/System.IO.Pipelines/System.IO.Pipelines.sln b/src/System.IO.Pipelines/System.IO.Pipelines.sln index 17264a346caf..fd5248d6e7d2 100644 --- a/src/System.IO.Pipelines/System.IO.Pipelines.sln +++ b/src/System.IO.Pipelines/System.IO.Pipelines.sln @@ -30,10 +30,10 @@ Global {9E984EB2-827E-4029-9647-FB5F8B67C553}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU {9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU {9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU - {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU - {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU - {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU - {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.Build.0 = Debug|Any CPU {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU diff --git a/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj b/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj index 75ac9fc474f9..7090b22b3e63 100644 --- a/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj +++ b/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj @@ -2,9 +2,10 @@ - + net46;netcore50;netcoreapp1.0;$(UAPvNextTFM);$(AllXamarinFrameworks) + diff --git a/src/System.IO.Pipelines/ref/Configurations.props b/src/System.IO.Pipelines/ref/Configurations.props index 5f3b2623edf6..e34b0bfd4e32 100644 --- a/src/System.IO.Pipelines/ref/Configurations.props +++ b/src/System.IO.Pipelines/ref/Configurations.props @@ -2,8 +2,9 @@ - netstandard1.3; + netstandard1.1; netstandard; + netcoreapp2.1; diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs b/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs index 6eccbcfdfff4..196126dbb074 100644 --- a/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs +++ b/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs @@ -84,7 +84,7 @@ protected PipeWriter() { } public abstract System.Memory GetMemory(int minimumLength = 0); public abstract System.Span GetSpan(int minimumLength = 0); public abstract void OnReaderCompleted(System.Action callback, object state); - public virtual System.IO.Pipelines.PipeAwaiter WriteAsync(System.ReadOnlyMemory source) { throw null; } + public virtual System.IO.Pipelines.PipeAwaiter WriteAsync(System.ReadOnlyMemory source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial struct ReadResult { diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj index b3541effa43f..eaf7297d3481 100644 --- a/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj +++ b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj @@ -14,10 +14,5 @@ - - - - - diff --git a/src/System.IO.Pipelines/src/Configurations.props b/src/System.IO.Pipelines/src/Configurations.props index 78953dfc8851..e34b0bfd4e32 100644 --- a/src/System.IO.Pipelines/src/Configurations.props +++ b/src/System.IO.Pipelines/src/Configurations.props @@ -2,7 +2,9 @@ + netstandard1.1; netstandard; + netcoreapp2.1; diff --git a/src/System.IO.Pipelines/src/Properties/InternalsVisibleTo.cs b/src/System.IO.Pipelines/src/Properties/InternalsVisibleTo.cs index 76ab3f9ae612..9bb191bf7b5a 100644 --- a/src/System.IO.Pipelines/src/Properties/InternalsVisibleTo.cs +++ b/src/System.IO.Pipelines/src/Properties/InternalsVisibleTo.cs @@ -2,8 +2,6 @@ // 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.Runtime.CompilerServices; [assembly: InternalsVisibleTo("System.IO.Pipelines.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010015c01ae1f50e8cc09ba9eac9147cf8fd9fce2cfe9f8dce4f7301c4132ca9fb50ce8cbf1df4dc18dd4d210e4345c744ecb3365ed327efdbc52603faa5e21daa11234c8c4a73e51f03bf192544581ebe107adee3a34928e39d04e524a9ce729d5090bfd7dad9d10c722c0def9ccc08ff0a03790e48bcd1f9b6c476063e1966a1c4")] diff --git a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj index 2fe8d336e01e..b66ab265ed50 100644 --- a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj +++ b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj @@ -3,11 +3,11 @@ {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} - System.Threading.Channels $(OutputPath)$(MSBuildProjectName).xml + $(DefineConstants);netcoreapp21 + $(DefineConstants);netstandard11 + $(DefineConstants);netstandard - - @@ -15,6 +15,8 @@ + + @@ -28,21 +30,26 @@ - + + + + + + + + + - - - \ No newline at end of file diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs index 1ab947e28f99..d2b2baa91de4 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs @@ -7,7 +7,7 @@ namespace System.IO.Pipelines { - internal class BufferSegment : IMemoryList + internal sealed class BufferSegment : IMemoryList { private OwnedMemory _ownedMemory; diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeReader.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeReader.cs new file mode 100644 index 000000000000..5f60d20df920 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeReader.cs @@ -0,0 +1,44 @@ +// 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.Threading; + +namespace System.IO.Pipelines +{ + /// + /// Default and implementation. + /// + public sealed partial class Pipe + { + private sealed class DefaultPipeReader : PipeReader, IPipeAwaiter + { + private readonly Pipe _pipe; + + public DefaultPipeReader(Pipe pipe) + { + _pipe = pipe; + } + + public override bool TryRead(out ReadResult result) => _pipe.TryRead(out result); + + public override PipeAwaiter ReadAsync(CancellationToken cancellationToken = default) => _pipe.ReadAsync(cancellationToken); + + public override void AdvanceTo(SequencePosition consumed) => _pipe.AdvanceReader(consumed); + + public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) => _pipe.AdvanceReader(consumed, examined); + + public override void CancelPendingRead() => _pipe.CancelPendingRead(); + + public override void Complete(Exception exception = null) => _pipe.CompleteReader(exception); + + public override void OnWriterCompleted(Action callback, object state) => _pipe.OnWriterCompleted(callback, state); + + public bool IsCompleted => _pipe.IsReadAsyncCompleted; + + public ReadResult GetResult() => _pipe.GetReadAsyncResult(); + + public void OnCompleted(Action continuation) => _pipe.OnReadAsyncCompleted(continuation); + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeWriter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeWriter.cs new file mode 100644 index 000000000000..349dbabf7c31 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeWriter.cs @@ -0,0 +1,46 @@ +// 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.Threading; + +namespace System.IO.Pipelines +{ + /// + /// Default and implementation. + /// + public sealed partial class Pipe + { + private sealed class DefaultPipeWriter : PipeWriter, IPipeAwaiter + { + private readonly Pipe _pipe; + + public DefaultPipeWriter(Pipe pipe) + { + _pipe = pipe; + } + + public override void Complete(Exception exception = null) => _pipe.CompleteWriter(exception); + + public override void CancelPendingFlush() => _pipe.CancelPendingFlush(); + + public override void OnReaderCompleted(Action callback, object state) => _pipe.OnReaderCompleted(callback, state); + + public override PipeAwaiter FlushAsync(CancellationToken cancellationToken = default) => _pipe.FlushAsync(cancellationToken); + + public override void Commit() => _pipe.Commit(); + + public override void Advance(int bytes) => _pipe.Advance(bytes); + + public override Memory GetMemory(int minimumLength = 0) => _pipe.GetMemory(minimumLength); + + public override Span GetSpan(int minimumLength = 0) => _pipe.GetMemory(minimumLength).Span; + + public bool IsCompleted => _pipe.IsFlushAsyncCompleted; + + public FlushResult GetResult() => _pipe.GetFlushAsyncResult(); + + public void OnCompleted(Action continuation) => _pipe.OnFlushAsyncCompleted(continuation); + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs index 7cf3ad719472..6043d49a2b59 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs @@ -1,5 +1,6 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// 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.Buffers; using System.Diagnostics; @@ -11,13 +12,13 @@ namespace System.IO.Pipelines /// /// Default and implementation. /// - public sealed class Pipe + public sealed partial class Pipe { private const int SegmentPoolSize = 16; - private static readonly Action _signalReaderAwaitable = state => ((Pipe)state).ReaderCancellationRequested(); - private static readonly Action _signalWriterAwaitable = state => ((Pipe)state).WriterCancellationRequested(); - private static readonly Action _invokeCompletionCallbacks = state => ((PipeCompletionCallbacks)state).Execute(); + private static readonly Action s_signalReaderAwaitable = state => ((Pipe)state).ReaderCancellationRequested(); + private static readonly Action s_signalWriterAwaitable = state => ((Pipe)state).WriterCancellationRequested(); + private static readonly Action s_invokeCompletionCallbacks = state => ((PipeCompletionCallbacks)state).Execute(); // This sync objects protects the following state: // 1. _commitHead & _commitHeadIndex @@ -27,12 +28,17 @@ public sealed class Pipe private readonly MemoryPool _pool; private readonly int _minimumSegmentSize; - private readonly long _maximumSizeHigh; - private readonly long _maximumSizeLow; + private readonly long _pauseWriterThreshold; + private readonly long _resumeWriterThreshold; private readonly PipeScheduler _readerScheduler; private readonly PipeScheduler _writerScheduler; + private readonly BufferSegment[] _bufferSegmentPool; + + private readonly DefaultPipeReader _reader; + private readonly DefaultPipeWriter _writer; + private long _length; private long _currentWriteLength; @@ -44,8 +50,6 @@ public sealed class Pipe private PipeCompletion _writerCompletion; private PipeCompletion _readerCompletion; - private readonly BufferSegment[] _bufferSegmentPool; - // The read head which is the extent of the IPipelineReader's consumed bytes private BufferSegment _readHead; private int _readHeadIndex; @@ -61,9 +65,6 @@ public sealed class Pipe private bool _disposed; - private DefaultPipeReader _reader; - private DefaultPipeWriter _writer; - internal long Length => _length; /// @@ -91,8 +92,8 @@ public Pipe(PipeOptions options) _pool = options.Pool; _minimumSegmentSize = options.MinimumSegmentSize; - _maximumSizeHigh = options.PauseWriterThreshold; - _maximumSizeLow = options.ResumeWriterThreshold; + _pauseWriterThreshold = options.PauseWriterThreshold; + _resumeWriterThreshold = options.ResumeWriterThreshold; _readerScheduler = options.ReaderScheduler ?? PipeScheduler.Inline; _writerScheduler = options.WriterScheduler ?? PipeScheduler.Inline; _readerAwaitable = new PipeAwaitable(completed: false); @@ -148,8 +149,6 @@ internal Memory GetMemory(int minimumSize) return _writingHead.AvailableMemory.Slice(_writingHead.End, _writingHead.WritableBytes); } - internal Span GetSpan(int minimumSize) => GetMemory(minimumSize).Span; - private BufferSegment AllocateWriteHeadUnsynchronized(int count) { BufferSegment segment = null; @@ -242,8 +241,8 @@ internal void CommitUnsynchronized() _length += _currentWriteLength; // Do not reset if reader is complete - if (_maximumSizeHigh > 0 && - _length >= _maximumSizeHigh && + if (_pauseWriterThreshold > 0 && + _length >= _pauseWriterThreshold && !_readerCompletion.IsCompleted) { _writerAwaitable.Reset(); @@ -300,7 +299,7 @@ internal PipeAwaiter FlushAsync(CancellationToken cancellationToken awaitable = _readerAwaitable.Complete(); - cancellationTokenRegistration = _writerAwaitable.AttachToken(cancellationToken, _signalWriterAwaitable, this); + cancellationTokenRegistration = _writerAwaitable.AttachToken(cancellationToken, s_signalWriterAwaitable, this); } cancellationTokenRegistration.Dispose(); @@ -330,7 +329,7 @@ internal void CompleteWriter(Exception exception) if (completionCallbacks != null) { - TrySchedule(_readerScheduler, _invokeCompletionCallbacks, completionCallbacks); + TrySchedule(_readerScheduler, s_invokeCompletionCallbacks, completionCallbacks); } TrySchedule(_readerScheduler, awaitable); @@ -378,8 +377,8 @@ internal void AdvanceReader(SequencePosition consumed, SequencePosition examined long oldLength = _length; _length -= consumedBytes; - if (oldLength >= _maximumSizeLow && - _length < _maximumSizeLow) + if (oldLength >= _resumeWriterThreshold && + _length < _resumeWriterThreshold) { continuation = _writerAwaitable.Complete(); } @@ -389,7 +388,7 @@ internal void AdvanceReader(SequencePosition consumed, SequencePosition examined // might be using tailspace if (consumed.Index == returnEnd.Length && _writingHead != returnEnd) { - var nextBlock = returnEnd.NextSegment; + BufferSegment nextBlock = returnEnd.NextSegment; if (_commitHead == returnEnd) { _commitHead = nextBlock; @@ -452,7 +451,7 @@ internal void CompleteReader(Exception exception) if (completionCallbacks != null) { - TrySchedule(_writerScheduler, _invokeCompletionCallbacks, completionCallbacks); + TrySchedule(_writerScheduler, s_invokeCompletionCallbacks, completionCallbacks); } TrySchedule(_writerScheduler, awaitable); @@ -478,7 +477,7 @@ internal void OnWriterCompleted(Action callback, object state if (completionCallbacks != null) { - TrySchedule(_readerScheduler, _invokeCompletionCallbacks, completionCallbacks); + TrySchedule(_readerScheduler, s_invokeCompletionCallbacks, completionCallbacks); } } @@ -517,7 +516,7 @@ internal void OnReaderCompleted(Action callback, object state if (completionCallbacks != null) { - TrySchedule(_writerScheduler, _invokeCompletionCallbacks, completionCallbacks); + TrySchedule(_writerScheduler, s_invokeCompletionCallbacks, completionCallbacks); } } @@ -530,7 +529,7 @@ internal PipeAwaiter ReadAsync(CancellationToken token) } lock (_sync) { - cancellationTokenRegistration = _readerAwaitable.AttachToken(token, _signalReaderAwaitable, this); + cancellationTokenRegistration = _readerAwaitable.AttachToken(token, s_signalReaderAwaitable, this); } cancellationTokenRegistration.Dispose(); return new PipeAwaiter(_reader); @@ -755,112 +754,5 @@ public void Reset() ResetState(); } } - - private sealed class DefaultPipeReader : PipeReader, IPipeAwaiter - { - private readonly Pipe _pipe; - - public DefaultPipeReader(Pipe pipe) - { - _pipe = pipe; - } - - public override bool TryRead(out ReadResult result) - { - return _pipe.TryRead(out result); - } - - public override PipeAwaiter ReadAsync(CancellationToken cancellationToken = default) - { - return _pipe.ReadAsync(cancellationToken); - } - - public override void AdvanceTo(SequencePosition consumed) - { - _pipe.AdvanceReader(consumed); - } - - public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) - { - _pipe.AdvanceReader(consumed, examined); - } - - public override void CancelPendingRead() - { - _pipe.CancelPendingRead(); - } - - public override void Complete(Exception exception = null) - { - _pipe.CompleteReader(exception); - } - - public override void OnWriterCompleted(Action callback, object state) - { - _pipe.OnWriterCompleted(callback, state); - } - - public bool IsCompleted => _pipe.IsReadAsyncCompleted; - - public ReadResult GetResult() => _pipe.GetReadAsyncResult(); - - public void OnCompleted(Action continuation) => _pipe.OnReadAsyncCompleted(continuation); - } - - private sealed class DefaultPipeWriter : PipeWriter, IPipeAwaiter - { - private readonly Pipe _pipe; - - public DefaultPipeWriter(Pipe pipe) - { - _pipe = pipe; - } - - public override void Complete(Exception exception = null) - { - _pipe.CompleteWriter(exception); - } - - public override void CancelPendingFlush() - { - _pipe.CancelPendingFlush(); - } - - public override void OnReaderCompleted(Action callback, object state) - { - _pipe.OnReaderCompleted(callback, state); - } - - public override PipeAwaiter FlushAsync(CancellationToken cancellationToken = default) - { - return _pipe.FlushAsync(cancellationToken); - } - - public override void Commit() - { - _pipe.Commit(); - } - - public override void Advance(int bytes) - { - _pipe.Advance(bytes); - } - - public override Memory GetMemory(int minimumLength = 0) - { - return _pipe.GetMemory(minimumLength); - } - - public override Span GetSpan(int minimumLength = 0) - { - return _pipe.GetSpan(minimumLength); - } - - public bool IsCompleted => _pipe.IsFlushAsyncCompleted; - - public FlushResult GetResult() => _pipe.GetFlushAsyncResult(); - - public void OnCompleted(Action continuation) => _pipe.OnFlushAsyncCompleted(continuation); - } } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs index 3bdcacbd7285..7507312cb37f 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeAwaitable.cs @@ -8,7 +8,7 @@ namespace System.IO.Pipelines { - [DebuggerDisplay("CancelledState: {_canceledState}, IsCompleted: {IsCompleted}")] + [DebuggerDisplay("CanceledState: {_canceledState}, IsCompleted: {IsCompleted}")] internal struct PipeAwaitable { private static readonly Action _awaitableIsCompleted = () => { }; @@ -67,7 +67,7 @@ public void Reset() // Change the state from observed -> not cancelled. // We only want to reset the cancelled state if it was observed - if (_canceledState == CanceledState.CancellationObserved) + if (_canceledState == CanceledState.CancelationObserved) { _canceledState = CanceledState.NotCanceled; } @@ -120,7 +120,7 @@ public bool ObserveCancelation() if (_canceledState >= CanceledState.CancellationPreRequested) { - _canceledState = CanceledState.CancellationObserved; + _canceledState = CanceledState.CancelationObserved; // Do not reset awaitable if we were not awaiting in the first place if (!isPrerequested) @@ -139,7 +139,7 @@ public bool ObserveCancelation() private enum CanceledState { NotCanceled = 0, - CancellationObserved = 1, + CancelationObserved = 1, CancellationPreRequested = 2, CancellationRequested = 3, } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs index 6e9600be95df..4f4611fb2751 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletion.cs @@ -12,10 +12,10 @@ namespace System.IO.Pipelines [DebuggerDisplay("IsCompleted: {" + nameof(IsCompleted) + "}")] internal struct PipeCompletion { - private static readonly ArrayPool CompletionCallbackPool = ArrayPool.Shared; + private static readonly ArrayPool s_completionCallbackPool = ArrayPool.Shared; + private static readonly Exception s_completedNoException = new Exception(); private const int InitialCallbacksSize = 1; - private static readonly Exception _completedNoException = new Exception(); private Exception _exception; @@ -29,7 +29,7 @@ public PipeCompletionCallbacks TryComplete(Exception exception = null) if (_exception == null) { // Set the exception object to the exception passed in or a sentinel value - _exception = exception ?? _completedNoException; + _exception = exception ?? s_completedNoException; } return GetCallbacks(); } @@ -38,7 +38,7 @@ public PipeCompletionCallbacks AddCallback(Action callback, o { if (_callbacks == null) { - _callbacks = CompletionCallbackPool.Rent(InitialCallbacksSize); + _callbacks = s_completionCallbackPool.Rent(InitialCallbacksSize); } int newIndex = _callbackCount; @@ -46,9 +46,9 @@ public PipeCompletionCallbacks AddCallback(Action callback, o if (newIndex == _callbacks.Length) { - PipeCompletionCallback[] newArray = CompletionCallbackPool.Rent(_callbacks.Length * 2); + PipeCompletionCallback[] newArray = s_completionCallbackPool.Rent(_callbacks.Length * 2); Array.Copy(_callbacks, newArray, _callbacks.Length); - CompletionCallbackPool.Return(_callbacks, clearArray: true); + s_completionCallbackPool.Return(_callbacks, clearArray: true); _callbacks = newArray; } @@ -71,9 +71,9 @@ public bool IsCompletedOrThrow() return false; } - if (_exception != _completedNoException) + if (_exception != s_completedNoException) { - ThrowFailed(); + ThrowLatchedException(); } return true; @@ -87,9 +87,9 @@ private PipeCompletionCallbacks GetCallbacks() return null; } - var callbacks = new PipeCompletionCallbacks(CompletionCallbackPool, + var callbacks = new PipeCompletionCallbacks(s_completionCallbackPool, _callbackCount, - _exception == _completedNoException ? null : _exception, + _exception == s_completedNoException ? null : _exception, _callbacks); _callbacks = null; @@ -105,7 +105,7 @@ public void Reset() } [MethodImpl(MethodImplOptions.NoInlining)] - private void ThrowFailed() + private void ThrowLatchedException() { ExceptionDispatchInfo.Capture(_exception).Throw(); } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallbacks.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallbacks.cs index 371e5e434b4e..e8e87baf15e1 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallbacks.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeCompletionCallbacks.cs @@ -7,7 +7,7 @@ namespace System.IO.Pipelines { - internal class PipeCompletionCallbacks + internal sealed class PipeCompletionCallbacks { private readonly ArrayPool _pool; private readonly int _count; @@ -33,7 +33,7 @@ public void Execute() { List exceptions = null; - for (var i = 0; i < _count; i++) + for (int i = 0; i < _count; i++) { PipeCompletionCallback callback = _callbacks[i]; try diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs index fb3ac117ded7..5ee5a8023a55 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeOptions.cs @@ -27,15 +27,14 @@ public PipeOptions( long resumeWriterThreshold = 0, int minimumSegmentSize = 2048) { - - if (resumeWriterThreshold < 0 && resumeWriterThreshold > pauseWriterThreshold) + if (pauseWriterThreshold < 0) { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.resumeWriterThreshold); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.pauseWriterThreshold); } - if (pauseWriterThreshold < 0) + if (resumeWriterThreshold < 0 && resumeWriterThreshold > pauseWriterThreshold) { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.pauseWriterThreshold); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.resumeWriterThreshold); } Pool = pool ?? MemoryPool.Shared; diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs index 223a1e58a387..caffd3643716 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeReaderState.cs @@ -1,5 +1,6 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// 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.Diagnostics; using System.Runtime.CompilerServices; @@ -47,12 +48,12 @@ public void End() } public bool IsActive => _state == State.Active; - } - internal enum State: byte - { - Inactive = 1, - Active = 2, - Tentative = 3 + internal enum State: byte + { + Inactive = 1, + Active = 2, + Tentative = 3 + } } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs index 353945f486ed..1729be0badf4 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeScheduler.cs @@ -9,18 +9,18 @@ namespace System.IO.Pipelines /// public abstract class PipeScheduler { - private static readonly ThreadPoolScheduler _threadPoolScheduler = new ThreadPoolScheduler(); - private static readonly InlineScheduler _inlineScheduler = new InlineScheduler(); + private static readonly ThreadPoolScheduler s_threadPoolScheduler = new ThreadPoolScheduler(); + private static readonly InlineScheduler s_inlineScheduler = new InlineScheduler(); /// /// The implementation that queues callbacks to thread pool /// - public static PipeScheduler ThreadPool => _threadPoolScheduler; + public static PipeScheduler ThreadPool => s_threadPoolScheduler; /// /// The implementation that runs callbacks inline /// - public static PipeScheduler Inline => _inlineScheduler; + public static PipeScheduler Inline => s_inlineScheduler; /// /// Requests to be run on scheduler diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs index a04140d010eb..dfd02cca5444 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs @@ -1,5 +1,6 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// 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.Buffers; using System.Threading; @@ -49,10 +50,10 @@ public abstract class PipeWriter : IBufferWriter /// /// Writes to the pipe and makes data accessible to /// - public virtual PipeAwaiter WriteAsync(ReadOnlyMemory source) + public virtual PipeAwaiter WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) { this.Write(source.Span); - return FlushAsync(); + return FlushAsync(cancellationToken); } } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs index c935c9b94524..f8702c035adf 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs @@ -1,5 +1,6 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// 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.Buffers; diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ResultFlags.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ResultFlags.cs index 9d7a26ae8aff..7a0453ccd813 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/ResultFlags.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ResultFlags.cs @@ -1,13 +1,14 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// 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. namespace System.IO.Pipelines { [Flags] internal enum ResultFlags : byte { - None = 0, - Canceled = 1, - Completed = 2 + None = 0x0, + Canceled = 0x1, + Completed = 0x2 } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetCoreApp21.cs similarity index 77% rename from src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.cs rename to src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetCoreApp21.cs index fcf233d97d83..04ba31cd620c 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetCoreApp21.cs @@ -11,29 +11,16 @@ internal sealed class ThreadPoolScheduler : PipeScheduler { public override void Schedule(Action action) { -#if NETCOREAPP2_1 // Queue to low contention local ThreadPool queue; rather than global queue as per Task System.Threading.ThreadPool.QueueUserWorkItem(_actionWaitCallback, action, preferLocal: true); -#elif NETSTANDARD2_0 - System.Threading.ThreadPool.QueueUserWorkItem(_actionWaitCallback, action); -#else - Task.Factory.StartNew(action); -#endif } public override void Schedule(Action action, object state) { -#if NETCOREAPP2_1 // Queue to low contention local ThreadPool queue; rather than global queue as per Task System.Threading.ThreadPool.QueueUserWorkItem(_actionObjectWaitCallback, new ActionObjectAsWaitCallback(action, state), preferLocal: true); -#elif NETSTANDARD2_0 - System.Threading.ThreadPool.QueueUserWorkItem(_actionObjectWaitCallback, new ActionObjectAsWaitCallback(action, state)); -#else - Task.Factory.StartNew(action, state); -#endif } -#if NETCOREAPP2_1 || NETSTANDARD2_0 private readonly static WaitCallback _actionWaitCallback = state => ((Action)state)(); private readonly static WaitCallback _actionObjectWaitCallback = state => ((ActionObjectAsWaitCallback)state).Run(); @@ -51,6 +38,5 @@ public ActionObjectAsWaitCallback(Action action, object state) public void Run() => _action(_state); } -#endif } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetStandard.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetStandard.cs new file mode 100644 index 000000000000..9f82de969f3e --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetStandard.cs @@ -0,0 +1,22 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace System.IO.Pipelines +{ + internal sealed class ThreadPoolScheduler : PipeScheduler + { + public override void Schedule(Action action) + { + Task.Factory.StartNew(action); + } + + public override void Schedule(Action action, object state) + { + Task.Factory.StartNew(action, state); + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetStandard20.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetStandard20.cs new file mode 100644 index 000000000000..9220a326b555 --- /dev/null +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThreadPoolScheduler.NetStandard20.cs @@ -0,0 +1,40 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace System.IO.Pipelines +{ + internal sealed class ThreadPoolScheduler : PipeScheduler + { + public override void Schedule(Action action) + { + System.Threading.ThreadPool.QueueUserWorkItem(_actionWaitCallback, action); + } + + public override void Schedule(Action action, object state) + { + System.Threading.ThreadPool.QueueUserWorkItem(_actionObjectWaitCallback, new ActionObjectAsWaitCallback(action, state)); + } + + private readonly static WaitCallback _actionWaitCallback = state => ((Action)state)(); + + private readonly static WaitCallback _actionObjectWaitCallback = state => ((ActionObjectAsWaitCallback)state).Run(); + + private sealed class ActionObjectAsWaitCallback + { + private Action _action; + private object _state; + + public ActionObjectAsWaitCallback(Action action, object state) + { + _action = action; + _state = state; + } + + public void Run() => _action(_state); + } + } +} diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs index fa43618d45b8..148cb20c704e 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ThrowHelper.cs @@ -6,159 +6,68 @@ namespace System.IO.Pipelines { - internal class ThrowHelper + internal static class ThrowHelper { - internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) { throw CreateArgumentOutOfRangeException(argument); } + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) => throw CreateArgumentOutOfRangeException(argument); [MethodImpl(MethodImplOptions.NoInlining)] - private static Exception CreateArgumentOutOfRangeException(ExceptionArgument argument) { return new ArgumentOutOfRangeException(argument.ToString()); } + private static Exception CreateArgumentOutOfRangeException(ExceptionArgument argument) => new ArgumentOutOfRangeException(argument.ToString()); - internal static void ThrowArgumentNullException(ExceptionArgument argument) { throw CreateArgumentNullException(argument); } + internal static void ThrowArgumentNullException(ExceptionArgument argument) => throw CreateArgumentNullException(argument); [MethodImpl(MethodImplOptions.NoInlining)] - private static Exception CreateArgumentNullException(ExceptionArgument argument) { return new ArgumentNullException(argument.ToString()); } - - public static void ThrowInvalidOperationException_NotWritingNoAlloc() - { - throw CreateInvalidOperationException_NotWritingNoAlloc(); - } + private static Exception CreateArgumentNullException(ExceptionArgument argument) => new ArgumentNullException(argument.ToString()); + public static void ThrowInvalidOperationException_NotWritingNoAlloc() { throw CreateInvalidOperationException_NotWritingNoAlloc(); } [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_NotWritingNoAlloc() - { - return new InvalidOperationException(SR.NoWritingOperation); - } - - public static void ThrowInvalidOperationException_AlreadyReading() - { - throw CreateInvalidOperationException_AlreadyReading(); - } + public static Exception CreateInvalidOperationException_NotWritingNoAlloc() => new InvalidOperationException(SR.NoWritingOperation); + public static void ThrowInvalidOperationException_AlreadyReading() => throw CreateInvalidOperationException_AlreadyReading(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_AlreadyReading() - { - return new InvalidOperationException(SR.ReadingIsInProgress); - } - - public static void ThrowInvalidOperationException_NoReadToComplete() - { - throw CreateInvalidOperationException_NoReadToComplete(); - } + public static Exception CreateInvalidOperationException_AlreadyReading() => new InvalidOperationException(SR.ReadingIsInProgress); + public static void ThrowInvalidOperationException_NoReadToComplete() => throw CreateInvalidOperationException_NoReadToComplete(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_NoReadToComplete() - { - return new InvalidOperationException(SR.NoReadingOperationToComplete); - } - - public static void ThrowInvalidOperationException_NoConcurrentOperation() - { - throw CreateInvalidOperationException_NoConcurrentOperation(); - } + public static Exception CreateInvalidOperationException_NoReadToComplete() => new InvalidOperationException(SR.NoReadingOperationToComplete); + public static void ThrowInvalidOperationException_NoConcurrentOperation() => throw CreateInvalidOperationException_NoConcurrentOperation(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_NoConcurrentOperation() - { - return new InvalidOperationException(SR.ConcurrentOperationsNotSupported); - } - - public static void ThrowInvalidOperationException_GetResultNotCompleted() - { - throw CreateInvalidOperationException_GetResultNotCompleted(); - } + public static Exception CreateInvalidOperationException_NoConcurrentOperation() => new InvalidOperationException(SR.ConcurrentOperationsNotSupported); + public static void ThrowInvalidOperationException_GetResultNotCompleted() => throw CreateInvalidOperationException_GetResultNotCompleted(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_GetResultNotCompleted() - { - return new InvalidOperationException(SR.GetResultBeforeCompleted); - } + public static Exception CreateInvalidOperationException_GetResultNotCompleted() => new InvalidOperationException(SR.GetResultBeforeCompleted); - public static void ThrowInvalidOperationException_NoWritingAllowed() - { - throw CreateInvalidOperationException_NoWritingAllowed(); - } + public static void ThrowInvalidOperationException_NoWritingAllowed() => throw CreateInvalidOperationException_NoWritingAllowed(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_NoWritingAllowed() - { - return new InvalidOperationException(SR.ReadingAfterCompleted); - } - - public static void ThrowInvalidOperationException_NoReadingAllowed() - { - throw CreateInvalidOperationException_NoReadingAllowed(); - } + public static Exception CreateInvalidOperationException_NoWritingAllowed() => new InvalidOperationException(SR.ReadingAfterCompleted); + public static void ThrowInvalidOperationException_NoReadingAllowed() => throw CreateInvalidOperationException_NoReadingAllowed(); [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception CreateInvalidOperationException_NoReadingAllowed() => new InvalidOperationException(SR.WritingAfterCompleted); - public static Exception CreateInvalidOperationException_NoReadingAllowed() - { - return new InvalidOperationException(SR.WritingAfterCompleted); - } - - public static void ThrowInvalidOperationException_CompleteWriterActiveWriter() - { - throw CreateInvalidOperationException_CompleteWriterActiveWriter(); - } - + public static void ThrowInvalidOperationException_CompleteWriterActiveWriter() => throw CreateInvalidOperationException_CompleteWriterActiveWriter(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_CompleteWriterActiveWriter() - { - return new InvalidOperationException(SR.CannotCompleteWhiteWriting); - } - - public static void ThrowInvalidOperationException_CompleteReaderActiveReader() - { - throw CreateInvalidOperationException_CompleteReaderActiveReader(); - } + public static Exception CreateInvalidOperationException_CompleteWriterActiveWriter() => new InvalidOperationException(SR.CannotCompleteWhiteWriting); + public static void ThrowInvalidOperationException_CompleteReaderActiveReader() => throw CreateInvalidOperationException_CompleteReaderActiveReader(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_CompleteReaderActiveReader() - { - return new InvalidOperationException(SR.CannotCompleteWhileReading); - } - - public static void ThrowInvalidOperationException_AdvancingPastBufferSize() - { - throw CreateInvalidOperationException_AdvancingPastBufferSize(); - } + public static Exception CreateInvalidOperationException_CompleteReaderActiveReader() => new InvalidOperationException(SR.CannotCompleteWhileReading); + public static void ThrowInvalidOperationException_AdvancingPastBufferSize() => throw CreateInvalidOperationException_AdvancingPastBufferSize(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_AdvancingPastBufferSize() - { - return new InvalidOperationException(SR.CannotAdvancePastCurrentBufferSize); - } - - public static void ThrowInvalidOperationException_BackpressureDeadlock() - { - throw CreateInvalidOperationException_BackpressureDeadlock(); - } + public static Exception CreateInvalidOperationException_AdvancingPastBufferSize() => new InvalidOperationException(SR.CannotAdvancePastCurrentBufferSize); + public static void ThrowInvalidOperationException_BackpressureDeadlock() => throw CreateInvalidOperationException_BackpressureDeadlock(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_BackpressureDeadlock() - { - return new InvalidOperationException(SR.BackpressureDeadlock); - } - - public static void ThrowInvalidOperationException_AdvanceToInvalidCursor() - { - throw CreateInvalidOperationException_AdvanceToInvalidCursor(); - } + public static Exception CreateInvalidOperationException_BackpressureDeadlock() => new InvalidOperationException(SR.BackpressureDeadlock); + public static void ThrowInvalidOperationException_AdvanceToInvalidCursor() => throw CreateInvalidOperationException_AdvanceToInvalidCursor(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_AdvanceToInvalidCursor() - { - return new InvalidOperationException(SR.AdvanceToInvalidCursor); - } - - public static void ThrowInvalidOperationException_ResetIncompleteReaderWriter() - { - throw CreateInvalidOperationException_ResetIncompleteReaderWriter(); - } + public static Exception CreateInvalidOperationException_AdvanceToInvalidCursor() => new InvalidOperationException(SR.AdvanceToInvalidCursor); + public static void ThrowInvalidOperationException_ResetIncompleteReaderWriter() => throw CreateInvalidOperationException_ResetIncompleteReaderWriter(); [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception CreateInvalidOperationException_ResetIncompleteReaderWriter() - { - return new InvalidOperationException(SR.ReaderAndWriterHasToBeCompleted); - } + public static Exception CreateInvalidOperationException_ResetIncompleteReaderWriter() => new InvalidOperationException(SR.ReaderAndWriterHasToBeCompleted); } internal enum ExceptionArgument @@ -167,9 +76,7 @@ internal enum ExceptionArgument bytesWritten, callback, options, - pauseWriterThreshold, - resumeWriterThreshold } } diff --git a/src/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs b/src/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs index fe3302ccef5d..3a3b328828a6 100644 --- a/src/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs +++ b/src/System.IO.Pipelines/tests/FlushAsyncCancellationTests.cs @@ -126,7 +126,7 @@ public void FlushAsyncNotCompletedAfterCancellation() } [Fact] - public void FlushAsyncNotCompletedAfterCancellationTokenCancelled() + public void FlushAsyncNotCompletedAfterCancellationTokenCanceled() { var onCompletedCalled = false; var cts = new CancellationTokenSource(); @@ -151,7 +151,7 @@ public void FlushAsyncNotCompletedAfterCancellationTokenCancelled() } [Fact] - public void FlushAsyncReturnsCanceledIfCancelledBeforeFlush() + public void FlushAsyncReturnsCanceledIfCanceledBeforeFlush() { PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); @@ -165,7 +165,7 @@ public void FlushAsyncReturnsCanceledIfCancelledBeforeFlush() } [Fact] - public void FlushAsyncReturnsCanceledIfFlushCancelled() + public void FlushAsyncReturnsCanceledIfFlushCanceled() { PipeWriter writableBuffer = Pipe.Writer.WriteEmpty(MaximumSizeHigh); PipeAwaiter flushAsync = writableBuffer.FlushAsync(); @@ -216,7 +216,7 @@ public void FlushAsyncReturnsIsCancelOnCancelPendingFlushBeforeGetResult() } [Fact] - public void FlushAsyncThrowsIfPassedCancelledCancellationToken() + public void FlushAsyncThrowsIfPassedCanceledCancellationToken() { var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.Cancel(); @@ -240,7 +240,7 @@ public async Task FlushAsyncWithNewCancellationTokenNotAffectedByPrevious() } [Fact] - public void GetResultThrowsIfFlushAsyncCancelledAfterOnCompleted() + public void GetResultThrowsIfFlushAsyncCanceledAfterOnCompleted() { var onCompletedCalled = false; var cancellationTokenSource = new CancellationTokenSource(); @@ -263,7 +263,7 @@ public void GetResultThrowsIfFlushAsyncCancelledAfterOnCompleted() } [Fact] - public void GetResultThrowsIfFlushAsyncCancelledBeforeOnCompleted() + public void GetResultThrowsIfFlushAsyncCanceledBeforeOnCompleted() { var onCompletedCalled = false; var cancellationTokenSource = new CancellationTokenSource(); diff --git a/src/System.IO.Pipelines/tests/PipeReaderWriterFacts.cs b/src/System.IO.Pipelines/tests/PipeReaderWriterFacts.cs index 88603a7519bd..78645ec848ec 100644 --- a/src/System.IO.Pipelines/tests/PipeReaderWriterFacts.cs +++ b/src/System.IO.Pipelines/tests/PipeReaderWriterFacts.cs @@ -88,7 +88,7 @@ public async Task AdvanceResetsCommitHeadIndex() } [Fact] - public async Task AdvanceShouldResetStateIfReadCancelled() + public async Task AdvanceShouldResetStateIfReadCanceled() { _pipe.Reader.CancelPendingRead(); @@ -436,7 +436,7 @@ public async Task ReaderShouldNotGetUnflushedBytesWithAppend() } [Fact] - public async Task ReadingCanBeCancelled() + public async Task ReadingCanBeCanceled() { var cts = new CancellationTokenSource(); cts.Token.Register(() => { _pipe.Writer.Complete(new OperationCanceledException(cts.Token)); }); @@ -608,5 +608,11 @@ public async Task TryReadAfterReadAsyncThrows() Assert.Throws(() => _pipe.Reader.TryRead(out _)); _pipe.Reader.AdvanceTo(readResult.Buffer.Start); } + + [Fact] + public void GetMemoryZeroReturnsNonEmpty() + { + Assert.True(_pipe.Writer.GetMemory(0).Length > 0); + } } } diff --git a/src/System.IO.Pipelines/tests/ReadAsyncCancellationTests.cs b/src/System.IO.Pipelines/tests/ReadAsyncCancellationTests.cs index 94a8fd10701c..53955b295a66 100644 --- a/src/System.IO.Pipelines/tests/ReadAsyncCancellationTests.cs +++ b/src/System.IO.Pipelines/tests/ReadAsyncCancellationTests.cs @@ -13,7 +13,7 @@ namespace System.IO.Pipelines.Tests public class ReadAsyncCancellationTests : PipeTest { [Fact] - public async Task AdvanceShouldResetStateIfReadCancelled() + public async Task AdvanceShouldResetStateIfReadCanceled() { Pipe.Reader.CancelPendingRead(); @@ -190,7 +190,7 @@ public void GetResultThrowsIfFlushAsyncTokenFiredAfterCancelPending() } [Fact] - public void GetResultThrowsIfReadAsyncCancelledAfterOnCompleted() + public void GetResultThrowsIfReadAsyncCanceledAfterOnCompleted() { var onCompletedCalled = false; var cancellationTokenSource = new CancellationTokenSource(); @@ -210,7 +210,7 @@ public void GetResultThrowsIfReadAsyncCancelledAfterOnCompleted() } [Fact] - public void GetResultThrowsIfReadAsyncCancelledBeforeOnCompleted() + public void GetResultThrowsIfReadAsyncCanceledBeforeOnCompleted() { var onCompletedCalled = false; var cancellationTokenSource = new CancellationTokenSource(); @@ -305,7 +305,7 @@ public void ReadAsyncNotCompletedAfterCancellation() } [Fact] - public void ReadAsyncNotCompletedAfterCancellationTokenCancelled() + public void ReadAsyncNotCompletedAfterCancellationTokenCanceled() { var onCompletedCalled = false; var cts = new CancellationTokenSource(); @@ -362,7 +362,7 @@ public void ReadAsyncReturnsIsCancelOnCancelPendingReadBeforeGetResult() } [Fact] - public void ReadAsyncThrowsIfPassedCancelledCancellationToken() + public void ReadAsyncThrowsIfPassedCanceledCancellationToken() { var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.Cancel(); @@ -388,7 +388,7 @@ public async Task ReadAsyncWithNewCancellationTokenNotAffectedByPrevious() } [Fact] - public async Task ReadingCanBeCancelled() + public async Task ReadingCanBeCanceled() { var cts = new CancellationTokenSource(); cts.Token.Register(() => { Pipe.Writer.Complete(new OperationCanceledException(cts.Token)); }); From 2d44625e288fd28ba644c813a9e0d23ecfc95ddb Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 12 Feb 2018 12:49:14 -0800 Subject: [PATCH 06/14] Project changeS --- src/System.IO.Pipelines/System.IO.Pipelines.sln | 4 ---- src/System.IO.Pipelines/dir.props | 2 ++ src/System.IO.Pipelines/ref/Configurations.props | 3 ++- src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj | 6 ++++++ src/System.IO.Pipelines/src/Configurations.props | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/System.IO.Pipelines/System.IO.Pipelines.sln b/src/System.IO.Pipelines/System.IO.Pipelines.sln index fd5248d6e7d2..baa7b5c0a7f3 100644 --- a/src/System.IO.Pipelines/System.IO.Pipelines.sln +++ b/src/System.IO.Pipelines/System.IO.Pipelines.sln @@ -30,10 +30,6 @@ Global {9E984EB2-827E-4029-9647-FB5F8B67C553}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU {9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU {9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU - {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.Build.0 = Debug|Any CPU {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU {9C524CA0-92FF-437B-B568-BCE8A794A69A}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU diff --git a/src/System.IO.Pipelines/dir.props b/src/System.IO.Pipelines/dir.props index 4356decc45ef..696b9e89a5eb 100644 --- a/src/System.IO.Pipelines/dir.props +++ b/src/System.IO.Pipelines/dir.props @@ -4,5 +4,7 @@ 4.0.0.0 Open + true + true \ No newline at end of file diff --git a/src/System.IO.Pipelines/ref/Configurations.props b/src/System.IO.Pipelines/ref/Configurations.props index e34b0bfd4e32..41873b4f7eb0 100644 --- a/src/System.IO.Pipelines/ref/Configurations.props +++ b/src/System.IO.Pipelines/ref/Configurations.props @@ -4,7 +4,8 @@ netstandard1.1; netstandard; - netcoreapp2.1; + netcoreapp; + uap; diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj index eaf7297d3481..bf55b76f8d34 100644 --- a/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj +++ b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj @@ -6,6 +6,12 @@ + + + + + + diff --git a/src/System.IO.Pipelines/src/Configurations.props b/src/System.IO.Pipelines/src/Configurations.props index e34b0bfd4e32..a18301b24dd0 100644 --- a/src/System.IO.Pipelines/src/Configurations.props +++ b/src/System.IO.Pipelines/src/Configurations.props @@ -4,7 +4,7 @@ netstandard1.1; netstandard; - netcoreapp2.1; + netcoreapp; From 0cfbb25249dc640059d10120ae044455764cc71a Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 12 Feb 2018 15:34:33 -0800 Subject: [PATCH 07/14] PR Comments, temporary undo src crosscompile --- src/System.IO.Pipelines/dir.props | 2 - .../ref/Configurations.props | 3 -- .../ref/System.IO.Pipelines.cs | 3 +- .../ref/System.IO.Pipelines.csproj | 11 +----- .../src/Configurations.props | 2 - .../src/System.IO.Pipelines.csproj | 3 -- .../IO/Pipelines/Pipe.DefaultPipeWriter.cs | 2 + .../src/System/IO/Pipelines/PipeWriter.cs | 5 ++- src/System.Memory/ref/System.Memory.cs | 7 ++-- .../src/System/Buffers/BuffersExtensions.cs | 37 +++++++++---------- .../src/System/Buffers/IBufferWriter.cs | 13 +++++-- 11 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/System.IO.Pipelines/dir.props b/src/System.IO.Pipelines/dir.props index 696b9e89a5eb..4356decc45ef 100644 --- a/src/System.IO.Pipelines/dir.props +++ b/src/System.IO.Pipelines/dir.props @@ -4,7 +4,5 @@ 4.0.0.0 Open - true - true \ No newline at end of file diff --git a/src/System.IO.Pipelines/ref/Configurations.props b/src/System.IO.Pipelines/ref/Configurations.props index 41873b4f7eb0..78953dfc8851 100644 --- a/src/System.IO.Pipelines/ref/Configurations.props +++ b/src/System.IO.Pipelines/ref/Configurations.props @@ -2,10 +2,7 @@ - netstandard1.1; netstandard; - netcoreapp; - uap; diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs b/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs index 196126dbb074..febe68076083 100644 --- a/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs +++ b/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs @@ -73,7 +73,7 @@ protected PipeScheduler() { } public abstract void Schedule(System.Action action); public abstract void Schedule(System.Action action, object state); } - public abstract partial class PipeWriter : System.Buffers.IBufferWriter + public abstract partial class PipeWriter : System.Buffers.IBufferWriter { protected PipeWriter() { } public abstract void Advance(int bytes); @@ -83,6 +83,7 @@ protected PipeWriter() { } public abstract System.IO.Pipelines.PipeAwaiter FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); public abstract System.Memory GetMemory(int minimumLength = 0); public abstract System.Span GetSpan(int minimumLength = 0); + public abstract int MaxBufferSize { get; } public abstract void OnReaderCompleted(System.Action callback, object state); public virtual System.IO.Pipelines.PipeAwaiter WriteAsync(System.ReadOnlyMemory source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj index bf55b76f8d34..001572721dd5 100644 --- a/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj +++ b/src/System.IO.Pipelines/ref/System.IO.Pipelines.csproj @@ -6,19 +6,12 @@ - - - - - - - - - + + diff --git a/src/System.IO.Pipelines/src/Configurations.props b/src/System.IO.Pipelines/src/Configurations.props index a18301b24dd0..78953dfc8851 100644 --- a/src/System.IO.Pipelines/src/Configurations.props +++ b/src/System.IO.Pipelines/src/Configurations.props @@ -2,9 +2,7 @@ - netstandard1.1; netstandard; - netcoreapp; diff --git a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj index b66ab265ed50..3f5e5609c1f7 100644 --- a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj +++ b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj @@ -4,9 +4,6 @@ {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} $(OutputPath)$(MSBuildProjectName).xml - $(DefineConstants);netcoreapp21 - $(DefineConstants);netstandard11 - $(DefineConstants);netstandard diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeWriter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeWriter.cs index 349dbabf7c31..81993f0a2aab 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeWriter.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.DefaultPipeWriter.cs @@ -36,6 +36,8 @@ public DefaultPipeWriter(Pipe pipe) public override Span GetSpan(int minimumLength = 0) => _pipe.GetMemory(minimumLength).Span; + public override int MaxBufferSize => _pipe._pool.MaxBufferSize; + public bool IsCompleted => _pipe.IsFlushAsyncCompleted; public FlushResult GetResult() => _pipe.GetFlushAsyncResult(); diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs index dfd02cca5444..199a5f403e63 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/PipeWriter.cs @@ -10,7 +10,7 @@ namespace System.IO.Pipelines /// /// Defines a class that provides a pipeline to which data can be written. /// - public abstract class PipeWriter : IBufferWriter + public abstract class PipeWriter : IBufferWriter { /// /// Marks the as being complete, meaning no more items will be written to it. @@ -47,6 +47,9 @@ public abstract class PipeWriter : IBufferWriter /// public abstract Span GetSpan(int minimumLength = 0); + /// + public abstract int MaxBufferSize { get; } + /// /// Writes to the pipe and makes data accessible to /// diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index cd649742450d..422656b2f798 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -216,11 +216,12 @@ public ref partial struct Enumerator } namespace System.Buffers { - public partial interface IBufferWriter + public partial interface IBufferWriter { - void Advance(int bytes); + void Advance(int count); System.Memory GetMemory(int minimumLength = 0); System.Span GetSpan(int minimumLength = 0); + int MaxBufferSize { get; } } public partial interface IMemoryList { @@ -279,7 +280,7 @@ public static partial class BuffersExtensions public static void CopyTo(this System.Buffers.ReadOnlySequence sequence, System.Span destination) { } public static System.Nullable PositionOf(this System.Buffers.ReadOnlySequence sequence, T value) where T : System.IEquatable { throw null; } public static T[] ToArray(this System.Buffers.ReadOnlySequence sequence) { throw null; } - public static void Write(this System.Buffers.IBufferWriter bufferWriter, ReadOnlySpan source) { } + public static void Write(this System.Buffers.IBufferWriter bufferWriter, ReadOnlySpan source) { } } public readonly partial struct ReadOnlySequence { diff --git a/src/System.Memory/src/System/Buffers/BuffersExtensions.cs b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs index 0822a4760138..df541ffcf6e6 100644 --- a/src/System.Memory/src/System/Buffers/BuffersExtensions.cs +++ b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs @@ -58,38 +58,35 @@ public static T[] ToArray(this ReadOnlySequence sequence) /// /// Writes contents of to /// - public static void Write(this IBufferWriter bufferWriter, ReadOnlySpan source) + public static void Write(this IBufferWriter bufferWriter, ReadOnlySpan source) { - Span span = bufferWriter.GetSpan(); + Span destination = bufferWriter.GetSpan(); // Fast path, try copying to the available memory directly - if (source.Length <= span.Length) + if (source.Length <= destination.Length) { - source.CopyTo(span); + source.CopyTo(destination); bufferWriter.Advance(source.Length); return; } - var remaining = source.Length; - var offset = 0; - - while (remaining > 0) + while (source.Length > 0) { - var writable = Math.Min(remaining, span.Length); - - span = bufferWriter.GetSpan(writable); - - if (writable == 0) + int writeSize; + if (destination.Length == 0) { - continue; + writeSize = Math.Min(source.Length, bufferWriter.MaxBufferSize); + destination = bufferWriter.GetSpan(writeSize); + } + else + { + writeSize = destination.Length; } - source.Slice(offset, writable).CopyTo(span); - - remaining -= writable; - offset += writable; - - bufferWriter.Advance(writable); + bufferWriter.Advance(writeSize); + source.Slice(0, writeSize).CopyTo(destination); + source = source.Slice(writeSize); + destination = default; } } } diff --git a/src/System.Memory/src/System/Buffers/IBufferWriter.cs b/src/System.Memory/src/System/Buffers/IBufferWriter.cs index c84e96551dca..ca24be0141e0 100644 --- a/src/System.Memory/src/System/Buffers/IBufferWriter.cs +++ b/src/System.Memory/src/System/Buffers/IBufferWriter.cs @@ -4,14 +4,14 @@ namespace System.Buffers { /// - /// Represents a sink + /// Represents a sink /// - public interface IBufferWriter + public interface IBufferWriter { /// - /// Notifies that amount of data was written to / + /// Notifies that amount of data was written to / /// - void Advance(int bytes); + void Advance(int count); /// /// Requests the of at least in size. @@ -24,5 +24,10 @@ public interface IBufferWriter /// If is equal to 0, currently available memory would get returned. /// Span GetSpan(int minimumLength = 0); + + /// + /// Returns the maximum buffer size supported by this . + /// + int MaxBufferSize { get; } } } From 1f7f6a2bb900b132689be5926c3fb132d7fe702d Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 12 Feb 2018 15:48:28 -0800 Subject: [PATCH 08/14] More PR comments --- .../src/System/IO/Pipelines/BufferSegment.cs | 2 +- .../src/System/IO/Pipelines/FlushResult.cs | 12 ++++++------ .../src/System/IO/Pipelines/Pipe.cs | 19 +++++++------------ .../src/System/IO/Pipelines/ReadResult.cs | 18 +++++++++--------- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs index d2b2baa91de4..4504e55f12ae 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegment.cs @@ -30,7 +30,7 @@ public int End get => _end; set { - Debug.Assert(Start - value <= AvailableMemory.Length); + Debug.Assert(value - Start <= AvailableMemory.Length); _end = value; Memory = AvailableMemory.Slice(Start, _end - Start); diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/FlushResult.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/FlushResult.cs index f40254684ab6..5eab31c0bd5b 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/FlushResult.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/FlushResult.cs @@ -9,34 +9,34 @@ namespace System.IO.Pipelines /// public struct FlushResult { - internal ResultFlags ResultFlags; + internal ResultFlags _resultFlags; /// /// Creates a new instance of setting and flags /// public FlushResult(bool isCanceled, bool isCompleted) { - ResultFlags = ResultFlags.None; + _resultFlags = ResultFlags.None; if (isCanceled) { - ResultFlags |= ResultFlags.Canceled; + _resultFlags |= ResultFlags.Canceled; } if (isCompleted) { - ResultFlags |= ResultFlags.Completed; + _resultFlags |= ResultFlags.Completed; } } /// /// True if the current operation was canceled, otherwise false. /// - public bool IsCanceled => (ResultFlags & ResultFlags.Canceled) != 0; + public bool IsCanceled => (_resultFlags & ResultFlags.Canceled) != 0; /// /// True if the is complete otherwise false /// - public bool IsCompleted => (ResultFlags & ResultFlags.Completed) != 0; + public bool IsCompleted => (_resultFlags & ResultFlags.Completed) != 0; } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs index 6043d49a2b59..31f598aae130 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs @@ -111,10 +111,6 @@ private void ResetState() _length = 0; } - /// - /// Allocates memory from the pipeline to write into. - /// - /// The minimum size buffer to allocate internal Memory GetMemory(int minimumSize) { if (_writerCompletion.IsCompleted) @@ -267,14 +263,13 @@ internal void Advance(int bytesWritten) Debug.Assert(_writingHead.Next == null); Memory buffer = _writingHead.AvailableMemory; - int bufferIndex = _writingHead.End + bytesWritten; - if (bufferIndex > buffer.Length) + if (_writingHead.End > buffer.Length - bytesWritten) { ThrowHelper.ThrowInvalidOperationException_AdvancingPastBufferSize(); } - _writingHead.End = bufferIndex; + _writingHead.End += bytesWritten; _currentWriteLength += bytesWritten; } else if (bytesWritten < 0) @@ -639,13 +634,13 @@ private void GetResult(ref ReadResult result) { if (_writerCompletion.IsCompletedOrThrow()) { - result.ResultFlags |= ResultFlags.Completed; + result._resultFlags |= ResultFlags.Completed; } bool isCanceled = _readerAwaitable.ObserveCancelation(); if (isCanceled) { - result.ResultFlags |= ResultFlags.Canceled; + result._resultFlags |= ResultFlags.Canceled; } // No need to read end if there is no head @@ -654,7 +649,7 @@ private void GetResult(ref ReadResult result) if (head != null) { // Reading commit head shared with writer - result.ResultBuffer = new ReadOnlySequence(head, _readHeadIndex, _commitHead, _commitHeadIndex - _commitHead.Start); + result._resultBuffer = new ReadOnlySequence(head, _readHeadIndex, _commitHead, _commitHeadIndex - _commitHead.Start); } if (isCanceled) @@ -682,11 +677,11 @@ internal FlushResult GetFlushAsyncResult() // Change the state from to be canceled -> observed if (_writerAwaitable.ObserveCancelation()) { - result.ResultFlags |= ResultFlags.Canceled; + result._resultFlags |= ResultFlags.Canceled; } if (_readerCompletion.IsCompletedOrThrow()) { - result.ResultFlags |= ResultFlags.Completed; + result._resultFlags |= ResultFlags.Completed; } } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs index f8702c035adf..7c587f199e0e 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/ReadResult.cs @@ -11,40 +11,40 @@ namespace System.IO.Pipelines /// public struct ReadResult { - internal ReadOnlySequence ResultBuffer; - internal ResultFlags ResultFlags; + internal ReadOnlySequence _resultBuffer; + internal ResultFlags _resultFlags; /// /// Creates a new instance of setting and flags /// public ReadResult(ReadOnlySequence buffer, bool isCanceled, bool isCompleted) { - ResultBuffer = buffer; - ResultFlags = ResultFlags.None; + _resultBuffer = buffer; + _resultFlags = ResultFlags.None; if (isCompleted) { - ResultFlags |= ResultFlags.Completed; + _resultFlags |= ResultFlags.Completed; } if (isCanceled) { - ResultFlags |= ResultFlags.Canceled; + _resultFlags |= ResultFlags.Canceled; } } /// /// The that was read /// - public ReadOnlySequence Buffer => ResultBuffer; + public ReadOnlySequence Buffer => _resultBuffer; /// /// True if the current operation was canceled, otherwise false. /// - public bool IsCanceled => (ResultFlags & ResultFlags.Canceled) != 0; + public bool IsCanceled => (_resultFlags & ResultFlags.Canceled) != 0; /// /// True if the is complete /// - public bool IsCompleted => (ResultFlags & ResultFlags.Completed) != 0; + public bool IsCompleted => (_resultFlags & ResultFlags.Completed) != 0; } } From 792c5cdda0a93f59428e58a1d830dac0c1176e87 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 13 Feb 2018 08:22:58 -0800 Subject: [PATCH 09/14] T --- src/System.Memory/ref/System.Memory.cs | 6 +++--- src/System.Memory/src/System/Buffers/BuffersExtensions.cs | 2 +- src/System.Memory/src/System/Buffers/IBufferWriter.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index 422656b2f798..0a55e89d5a43 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -219,8 +219,8 @@ namespace System.Buffers public partial interface IBufferWriter { void Advance(int count); - System.Memory GetMemory(int minimumLength = 0); - System.Span GetSpan(int minimumLength = 0); + System.Memory GetMemory(int minimumLength = 0); + System.Span GetSpan(int minimumLength = 0); int MaxBufferSize { get; } } public partial interface IMemoryList @@ -280,7 +280,7 @@ public static partial class BuffersExtensions public static void CopyTo(this System.Buffers.ReadOnlySequence sequence, System.Span destination) { } public static System.Nullable PositionOf(this System.Buffers.ReadOnlySequence sequence, T value) where T : System.IEquatable { throw null; } public static T[] ToArray(this System.Buffers.ReadOnlySequence sequence) { throw null; } - public static void Write(this System.Buffers.IBufferWriter bufferWriter, ReadOnlySpan source) { } + public static void Write(this System.Buffers.IBufferWriter bufferWriter, ReadOnlySpan source) { } } public readonly partial struct ReadOnlySequence { diff --git a/src/System.Memory/src/System/Buffers/BuffersExtensions.cs b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs index df541ffcf6e6..7cf9b0c60998 100644 --- a/src/System.Memory/src/System/Buffers/BuffersExtensions.cs +++ b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs @@ -58,7 +58,7 @@ public static T[] ToArray(this ReadOnlySequence sequence) /// /// Writes contents of to /// - public static void Write(this IBufferWriter bufferWriter, ReadOnlySpan source) + public static void Write(this IBufferWriter bufferWriter, ReadOnlySpan source) { Span destination = bufferWriter.GetSpan(); diff --git a/src/System.Memory/src/System/Buffers/IBufferWriter.cs b/src/System.Memory/src/System/Buffers/IBufferWriter.cs index ca24be0141e0..8c6a97e691d5 100644 --- a/src/System.Memory/src/System/Buffers/IBufferWriter.cs +++ b/src/System.Memory/src/System/Buffers/IBufferWriter.cs @@ -17,13 +17,13 @@ public interface IBufferWriter /// Requests the of at least in size. /// If is equal to 0, currently available memory would get returned. /// - Memory GetMemory(int minimumLength = 0); + Memory GetMemory(int minimumLength = 0); /// /// Requests the of at least in size. /// If is equal to 0, currently available memory would get returned. /// - Span GetSpan(int minimumLength = 0); + Span GetSpan(int minimumLength = 0); /// /// Returns the maximum buffer size supported by this . From 82be16bb63124e8113ffb339b3d81c009996f957 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 13 Feb 2018 08:46:24 -0800 Subject: [PATCH 10/14] IDuplexPipe --- src/System.IO.Pipelines/ref/System.IO.Pipelines.cs | 2 +- .../src/System/IO/Pipelines/IPipeConnection.cs | 2 +- src/System.Memory/src/System/Buffers/BuffersExtensions.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs b/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs index febe68076083..40c029560d2e 100644 --- a/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs +++ b/src/System.IO.Pipelines/ref/System.IO.Pipelines.cs @@ -14,7 +14,7 @@ public partial struct FlushResult public bool IsCanceled { get { throw null; } } public bool IsCompleted { get { throw null; } } } - public partial interface IDuplexPipe : System.IDisposable + public partial interface IDuplexPipe { System.IO.Pipelines.PipeReader Input { get; } System.IO.Pipelines.PipeWriter Output { get; } diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeConnection.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeConnection.cs index f2f147b5726c..20f1e67e6942 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeConnection.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/IPipeConnection.cs @@ -7,7 +7,7 @@ namespace System.IO.Pipelines /// /// Defines a class that provides a duplex pipe from which data can be read from and written to. /// - public interface IDuplexPipe : IDisposable + public interface IDuplexPipe { /// /// Gets the half of the duplex pipe. diff --git a/src/System.Memory/src/System/Buffers/BuffersExtensions.cs b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs index 7cf9b0c60998..4e9a5c1c8970 100644 --- a/src/System.Memory/src/System/Buffers/BuffersExtensions.cs +++ b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs @@ -60,7 +60,7 @@ public static T[] ToArray(this ReadOnlySequence sequence) /// public static void Write(this IBufferWriter bufferWriter, ReadOnlySpan source) { - Span destination = bufferWriter.GetSpan(); + Span destination = bufferWriter.GetSpan(); // Fast path, try copying to the available memory directly if (source.Length <= destination.Length) From bb0d1299434fcaeabaecef5c602552cdcbe87359 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 13 Feb 2018 10:39:47 -0800 Subject: [PATCH 11/14] NetCoreapp --- src/System.IO.Pipelines/src/Configurations.props | 1 + src/System.IO.Pipelines/src/System.IO.Pipelines.csproj | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/System.IO.Pipelines/src/Configurations.props b/src/System.IO.Pipelines/src/Configurations.props index 78953dfc8851..271a4be4c93c 100644 --- a/src/System.IO.Pipelines/src/Configurations.props +++ b/src/System.IO.Pipelines/src/Configurations.props @@ -3,6 +3,7 @@ netstandard; + netcoreapp; diff --git a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj index 3f5e5609c1f7..61a378dcd69c 100644 --- a/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj +++ b/src/System.IO.Pipelines/src/System.IO.Pipelines.csproj @@ -29,7 +29,7 @@ - + @@ -46,7 +46,9 @@ + + \ No newline at end of file From 200bae71172c2aaae4698eb7ab5f14d72d678bea Mon Sep 17 00:00:00 2001 From: Jose Perez Rodriguez Date: Tue, 13 Feb 2018 11:24:31 -0800 Subject: [PATCH 12/14] Fixing pkgproj --- src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj b/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj index 7090b22b3e63..e4cc836f1880 100644 --- a/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj +++ b/src/System.IO.Pipelines/pkg/System.IO.Pipelines.pkgproj @@ -3,7 +3,7 @@ - net46;netcore50;netcoreapp1.0;$(UAPvNextTFM);$(AllXamarinFrameworks) + net461;netcoreapp2.0;$(AllXamarinFrameworks) From bb72f2e32593383ec3862d064cc8916ba5c4daa4 Mon Sep 17 00:00:00 2001 From: Jose Perez Rodriguez Date: Tue, 13 Feb 2018 14:24:40 -0800 Subject: [PATCH 13/14] Fix test project --- src/System.IO.Pipelines/tests/Configurations.props | 2 +- .../tests/System.IO.Pipelines.Tests.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/System.IO.Pipelines/tests/Configurations.props b/src/System.IO.Pipelines/tests/Configurations.props index 78953dfc8851..d3ac8a63c74a 100644 --- a/src/System.IO.Pipelines/tests/Configurations.props +++ b/src/System.IO.Pipelines/tests/Configurations.props @@ -2,7 +2,7 @@ - netstandard; + netcoreapp; diff --git a/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj b/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj index ffbfb1305576..76493ea47745 100644 --- a/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj +++ b/src/System.IO.Pipelines/tests/System.IO.Pipelines.Tests.csproj @@ -4,8 +4,8 @@ {9E984EB2-827E-4029-9647-FB5F8B67C553} - - + + From 17b00a58129e659cf01a1cb6c045c400f8e0c984 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 13 Feb 2018 14:32:58 -0800 Subject: [PATCH 14/14] More comments --- .../src/System/Buffers/BuffersExtensions.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/System.Memory/src/System/Buffers/BuffersExtensions.cs b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs index 4e9a5c1c8970..c69d8340962a 100644 --- a/src/System.Memory/src/System/Buffers/BuffersExtensions.cs +++ b/src/System.Memory/src/System/Buffers/BuffersExtensions.cs @@ -72,19 +72,16 @@ public static void Write(this IBufferWriter bufferWriter, ReadOnlySpan while (source.Length > 0) { - int writeSize; + int writeSize = destination.Length; + if (destination.Length == 0) { writeSize = Math.Min(source.Length, bufferWriter.MaxBufferSize); destination = bufferWriter.GetSpan(writeSize); } - else - { - writeSize = destination.Length; - } - bufferWriter.Advance(writeSize); source.Slice(0, writeSize).CopyTo(destination); + bufferWriter.Advance(writeSize); source = source.Slice(writeSize); destination = default; }