From 57807066727c20aa84a21285086760b2593393ec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 30 Mar 2021 12:05:14 -0700 Subject: [PATCH 1/8] Add test for crash --- .../AbstractPersistentStorageTests.cs | 4 ++-- .../CloudCachePersistentStorageTests.cs | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs index d51cb83ca8fd7..d843d842d2106 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs @@ -86,13 +86,13 @@ public void Dispose() _persistentFolderRoot.Dispose(); } - private string GetData1(Size size) + protected string GetData1(Size size) => size == Size.Small ? SmallData1 : size == Size.Medium ? MediumData1 : LargeData1; private string GetData2(Size size) => size == Size.Small ? SmallData2 : size == Size.Medium ? MediumData2 : LargeData2; - private Checksum? GetChecksum1(bool withChecksum) + private protected Checksum? GetChecksum1(bool withChecksum) => withChecksum ? s_checksum1 : null; private Checksum? GetChecksum2(bool withChecksum) diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs index feb9fbd8b2fdd..503f49e61af8b 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs @@ -4,12 +4,14 @@ using System; using System.Linq; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks; +using Xunit; namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices { @@ -35,5 +37,25 @@ internal override AbstractPersistentStorageService GetStorageService( } }); } + + [Theory] + [CombinatorialData] + public async Task PersistentService_ReadByteTwice(Size size, bool withChecksum) + { + var solution = CreateOrOpenSolution(); + var streamName1 = "PersistentService_ReadByteTwice"; + + await using (var storage = await GetStorageAsync(solution)) + { + Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); + } + + await using (var storage = await GetStorageAsync(solution)) + { + using var stream = await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)); + stream.ReadByte(); + stream.ReadByte(); + } + } } } From c88845be3a97bf64cb35abd44dd59e896b547231 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 30 Mar 2021 12:12:24 -0700 Subject: [PATCH 2/8] Add fix --- .../Core/Def/Storage/CloudCachePersistentStorage.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs index d538d67739f67..9028767a1589a 100644 --- a/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs +++ b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.PersistentStorage; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.VisualStudio.RpcContracts.Caching; +using Nerdbank.Streams; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Storage @@ -169,10 +170,8 @@ private async Task ChecksumMatchesAsync(string name, Checksum checksum, Ca pipe.Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); if (readResult.IsCompleted) - break; + return readResult.Buffer.AsStream(); } - - return pipe.Reader.AsStream(); } public sealed override Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) From ed42e4455714ce781528db5d1bf840cc20a16e7c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 30 Mar 2021 12:18:13 -0700 Subject: [PATCH 3/8] Move test up --- .../AbstractPersistentStorageTests.cs | 24 +++++++++++++++++-- .../CloudCachePersistentStorageTests.cs | 20 ---------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs index d843d842d2106..09ffc4b65775b 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs @@ -86,13 +86,13 @@ public void Dispose() _persistentFolderRoot.Dispose(); } - protected string GetData1(Size size) + private string GetData1(Size size) => size == Size.Small ? SmallData1 : size == Size.Medium ? MediumData1 : LargeData1; private string GetData2(Size size) => size == Size.Small ? SmallData2 : size == Size.Medium ? MediumData2 : LargeData2; - private protected Checksum? GetChecksum1(bool withChecksum) + private Checksum? GetChecksum1(bool withChecksum) => withChecksum ? s_checksum1 : null; private Checksum? GetChecksum2(bool withChecksum) @@ -786,6 +786,26 @@ public void CacheDirectoryShouldNotBeAtRoot() Assert.False(location?.StartsWith("/") ?? false); } + [Theory] + [CombinatorialData] + public async Task PersistentService_ReadByteTwice(Size size, bool withChecksum) + { + var solution = CreateOrOpenSolution(); + var streamName1 = "PersistentService_ReadByteTwice"; + + await using (var storage = await GetStorageAsync(solution)) + { + Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); + } + + await using (var storage = await GetStorageAsync(solution)) + { + using var stream = await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)); + stream.ReadByte(); + stream.ReadByte(); + } + } + [PartNotDiscoverable] [ExportWorkspaceService(typeof(IPersistentStorageLocationService), layer: ServiceLayer.Test), Shared] private class TestPersistentStorageLocationService : DefaultPersistentStorageLocationService diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs index 503f49e61af8b..6a20671013c83 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs @@ -37,25 +37,5 @@ internal override AbstractPersistentStorageService GetStorageService( } }); } - - [Theory] - [CombinatorialData] - public async Task PersistentService_ReadByteTwice(Size size, bool withChecksum) - { - var solution = CreateOrOpenSolution(); - var streamName1 = "PersistentService_ReadByteTwice"; - - await using (var storage = await GetStorageAsync(solution)) - { - Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); - } - - await using (var storage = await GetStorageAsync(solution)) - { - using var stream = await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)); - stream.ReadByte(); - stream.ReadByte(); - } - } } } From 3c3c58a8cc3c4fe778233ce78f39d2cf0bedae84 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 30 Mar 2021 12:19:06 -0700 Subject: [PATCH 4/8] Move test up --- .../Test/PersistentStorage/CloudCachePersistentStorageTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs index 6a20671013c83..feb9fbd8b2fdd 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/CloudCachePersistentStorageTests.cs @@ -4,14 +4,12 @@ using System; using System.Linq; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks; -using Xunit; namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices { From ea7a422a3c3edbaf9966135168070b4b8844ad7f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 31 Mar 2021 10:45:11 -0700 Subject: [PATCH 5/8] Update version --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 4a74f6c2dfcbe..14b2a0807dc47 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -192,7 +192,7 @@ 0.1.0 6.6.0.161 4.10.1 - 2.6.81 + 2.7.62-alpha 4.0.0-rc-2048 4.8.0 1.1.0-beta1-62506-02 From 296674d2fc9d143fbd627b9709839b1a308cadeb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 31 Mar 2021 16:41:05 -0700 Subject: [PATCH 6/8] move to new api --- src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj | 1 + .../Def/Microsoft.VisualStudio.LanguageServices.csproj | 1 + .../Core/Def/Storage/CloudCachePersistentStorage.cs | 9 +-------- .../Microsoft.CodeAnalysis.Remote.ServiceHub.csproj | 1 + 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj index 6ff0a32ca7726..c56107684204e 100644 --- a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj +++ b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj @@ -38,6 +38,7 @@ + diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index cfd6cf61be6b8..324c659f597a5 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -169,6 +169,7 @@ + diff --git a/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs index 9028767a1589a..d55db61cfdac8 100644 --- a/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs +++ b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs @@ -164,14 +164,7 @@ private async Task ChecksumMatchesAsync(string name, Checksum checksum, Ca // and then pass that out. This should not be a problem in practice as PipeReader internally intelligently // uses and pools reasonable sized buffers, preventing us from exacerbating the GC or causing LOH // allocations. - while (true) - { - var readResult = await pipe.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); - pipe.Reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); - - if (readResult.IsCompleted) - return readResult.Buffer.AsStream(); - } + return await pipe.Reader.AsPrebufferedStreamAsync(cancellationToken).ConfigureAwait(false); } public sealed override Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) diff --git a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj index 4e009fb8a1fea..f90703447d980 100644 --- a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj +++ b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj @@ -29,6 +29,7 @@ + From 8a527f98a5f1d1dd4f1fab4116a94c5a5ace49a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 31 Mar 2021 23:45:16 -0700 Subject: [PATCH 7/8] Switch to copy of streams --- eng/Versions.props | 2 +- .../IdeCoreBenchmarks.csproj | 1 + .../Storage/CloudCachePersistentStorage.cs | 22 +- .../Nerdbank/ReadOnlySequenceStream.cs | 262 ++++++++++++++++++ ...soft.CodeAnalysis.Remote.ServiceHub.csproj | 1 + 5 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs diff --git a/eng/Versions.props b/eng/Versions.props index eacbb83ed125d..a5adc8eff42b0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -192,7 +192,7 @@ 0.1.0 6.6.0.161 4.10.1 - 2.7.62-alpha + 2.6.81 4.0.0-rc-2048 4.8.0 1.1.0-beta1-62506-02 diff --git a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj index c56107684204e..75fb67aa863ff 100644 --- a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj +++ b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj @@ -16,6 +16,7 @@ + diff --git a/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs index d55db61cfdac8..6f19267631ff3 100644 --- a/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs +++ b/src/VisualStudio/Core/Def/Storage/CloudCachePersistentStorage.cs @@ -164,7 +164,27 @@ private async Task ChecksumMatchesAsync(string name, Checksum checksum, Ca // and then pass that out. This should not be a problem in practice as PipeReader internally intelligently // uses and pools reasonable sized buffers, preventing us from exacerbating the GC or causing LOH // allocations. - return await pipe.Reader.AsPrebufferedStreamAsync(cancellationToken).ConfigureAwait(false); + return await AsPrebufferedStreamAsync(pipe.Reader, cancellationToken).ConfigureAwait(false); + } + + private static async Task AsPrebufferedStreamAsync(PipeReader pipeReader, CancellationToken cancellationToken = default) + { + while (true) + { + // Read and immediately report all bytes as "examined" so that the next ReadAsync call will block till more bytes come in. + // The goal here is to force the PipeReader to buffer everything internally (even if it were to exceed its natural writer threshold limit). + ReadResult readResult = await pipeReader.ReadAsync(cancellationToken).ConfigureAwait(false); + pipeReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End); + + if (readResult.IsCompleted) + { + // After having buffered and "examined" all the bytes, the stream returned from PipeReader.AsStream() would fail + // because it may not "examine" all bytes at once. + // Instead, we'll create our own Stream over just the buffer itself, and recycle the buffers when the stream is disposed + // the way the stream returned from PipeReader.AsStream() would have. + return new ReadOnlySequenceStream(readResult.Buffer, reader => ((PipeReader)reader!).Complete(), pipeReader); + } + } } public sealed override Task WriteStreamAsync(string name, Stream stream, Checksum? checksum, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs b/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs new file mode 100644 index 0000000000000..2f19df3895aa8 --- /dev/null +++ b/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs @@ -0,0 +1,262 @@ +// 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. + +// Copied from https://raw.githubusercontent.com/AArnott/Nerdbank.Streams/2b142fa6a38b15e4b06ecc53bf073aa49fd1de34/src/Nerdbank.Streams/ReadOnlySequenceStream.cs +// Remove once we move to Nerdbank.Streams 2.7.62-alpha + +namespace Nerdbank.Streams +{ + using System; + using System.Buffers; + using System.IO; + using System.Runtime.InteropServices; + using System.Threading; + using System.Threading.Tasks; + using Microsoft; + + internal class ReadOnlySequenceStream : Stream, IDisposableObservable + { + private static readonly Task TaskOfZero = Task.FromResult(0); + + private readonly Action? disposeAction; + private readonly object? disposeActionArg; + + /// + /// A reusable task if two consecutive reads return the same number of bytes. + /// + private Task? lastReadTask; + + private ReadOnlySequence readOnlySequence; + + private SequencePosition position; + + internal ReadOnlySequenceStream(ReadOnlySequence readOnlySequence, Action? disposeAction, object? disposeActionArg) + { + this.readOnlySequence = readOnlySequence; + this.disposeAction = disposeAction; + this.disposeActionArg = disposeActionArg; + this.position = readOnlySequence.Start; + } + + /// + public override bool CanRead => !this.IsDisposed; + + /// + public override bool CanSeek => !this.IsDisposed; + + /// + public override bool CanWrite => false; + + /// + public override long Length => this.ReturnOrThrowDisposed(this.readOnlySequence.Length); + + /// + public override long Position + { + get => this.readOnlySequence.Slice(0, this.position).Length; + set + { + Requires.Range(value >= 0, nameof(value)); + this.position = this.readOnlySequence.GetPosition(value, this.readOnlySequence.Start); + } + } + + /// + public bool IsDisposed { get; private set; } + + /// + public override void Flush() => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override Task FlushAsync(CancellationToken cancellationToken) => throw this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override int Read(byte[] buffer, int offset, int count) + { + ReadOnlySequence remaining = this.readOnlySequence.Slice(this.position); + ReadOnlySequence toCopy = remaining.Slice(0, Math.Min(count, remaining.Length)); + this.position = toCopy.End; + toCopy.CopyTo(buffer.AsSpan(offset, count)); + return (int)toCopy.Length; + } + + /// + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + int bytesRead = this.Read(buffer, offset, count); + if (bytesRead == 0) + { + return TaskOfZero; + } + + if (this.lastReadTask?.Result == bytesRead) + { + return this.lastReadTask; + } + else + { + return this.lastReadTask = Task.FromResult(bytesRead); + } + } + + /// + public override int ReadByte() + { + ReadOnlySequence remaining = this.readOnlySequence.Slice(this.position); + if (remaining.Length > 0) + { + byte result = remaining.First.Span[0]; + this.position = this.readOnlySequence.GetPosition(1, this.position); + return result; + } + else + { + return -1; + } + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + Verify.NotDisposed(this); + + SequencePosition relativeTo; + switch (origin) + { + case SeekOrigin.Begin: + relativeTo = this.readOnlySequence.Start; + break; + case SeekOrigin.Current: + if (offset >= 0) + { + relativeTo = this.position; + } + else + { + relativeTo = this.readOnlySequence.Start; + offset += this.Position; + } + + break; + case SeekOrigin.End: + if (offset >= 0) + { + relativeTo = this.readOnlySequence.End; + } + else + { + relativeTo = this.readOnlySequence.Start; + offset += this.Position; + } + + break; + default: + throw new ArgumentOutOfRangeException(nameof(origin)); + } + + this.position = this.readOnlySequence.GetPosition(offset, relativeTo); + return this.Position; + } + + /// + public override void SetLength(long value) => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override void Write(byte[] buffer, int offset, int count) => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override void WriteByte(byte value) => this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + foreach (var segment in this.readOnlySequence) + { + await WriteAsync(destination, segment, cancellationToken).ConfigureAwait(false); + } + } + + private static ValueTask WriteAsync(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + Requires.NotNull(stream, nameof(stream)); + + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment array)) + { + return new ValueTask(stream.WriteAsync(array.Array!, array.Offset, array.Count, cancellationToken)); + } + else + { + byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + buffer.Span.CopyTo(sharedBuffer); + return new ValueTask(FinishWriteAsync(stream.WriteAsync(sharedBuffer, 0, buffer.Length, cancellationToken), sharedBuffer)); + } + + async Task FinishWriteAsync(Task writeTask, byte[] localBuffer) + { + try + { + await writeTask.ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(localBuffer); + } + } + } + +#if SPAN_BUILTIN + + /// + public override int Read(Span buffer) + { + ReadOnlySequence remaining = this.readOnlySequence.Slice(this.position); + ReadOnlySequence toCopy = remaining.Slice(0, Math.Min(buffer.Length, remaining.Length)); + this.position = toCopy.End; + toCopy.CopyTo(buffer); + return (int)toCopy.Length; + } + + /// + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new ValueTask(this.Read(buffer.Span)); + } + + /// + public override void Write(ReadOnlySpan buffer) => throw this.ThrowDisposedOr(new NotSupportedException()); + + /// + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => throw this.ThrowDisposedOr(new NotSupportedException()); + +#endif + + /// + protected override void Dispose(bool disposing) + { + if (!this.IsDisposed) + { + this.IsDisposed = true; + this.disposeAction?.Invoke(this.disposeActionArg); + base.Dispose(disposing); + } + } + + private T ReturnOrThrowDisposed(T value) + { + Verify.NotDisposed(this); + return value; + } + + private Exception ThrowDisposedOr(Exception ex) + { + Verify.NotDisposed(this); + throw ex; + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj index f90703447d980..5ad1f662f2edb 100644 --- a/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj +++ b/src/Workspaces/Remote/ServiceHub/Microsoft.CodeAnalysis.Remote.ServiceHub.csproj @@ -40,6 +40,7 @@ + From f8e3f7c07d3e251bbe18f7cf6423a7fc15a55812 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 1 Apr 2021 01:04:09 -0700 Subject: [PATCH 8/8] Make readonly --- .../Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs b/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs index 2f19df3895aa8..eee38d978c543 100644 --- a/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs +++ b/src/VisualStudio/Core/Def/Storage/Nerdbank/ReadOnlySequenceStream.cs @@ -27,7 +27,7 @@ internal class ReadOnlySequenceStream : Stream, IDisposableObservable /// private Task? lastReadTask; - private ReadOnlySequence readOnlySequence; + private readonly ReadOnlySequence readOnlySequence; private SequencePosition position;