diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 0ee90aae8212d..1673d25719ea4 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -237,7 +237,10 @@ static void GetStorageInfoFromTemporaryStorage( } // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. - stream = storageHandle.ReadFromTemporaryStorage(CancellationToken.None); + // The ITemporaryStorageStreamHandle should have given us an UnmanagedMemoryStream + // since this only runs on Windows for VS. + stream = (UnmanagedMemoryStream)storageHandle.ReadFromTemporaryStorage(CancellationToken.None); + // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index b5d561fd2c74e..fac5e87175dab 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -43,7 +43,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private readonly SolutionServices _workspaceServices; - private readonly TemporaryStorageService _storageService; + private readonly Lazy _storageService; private readonly ITextFactoryService _textService; private readonly IDocumentationProviderService? _documentationService; private readonly IAnalyzerAssemblyLoaderProvider _analyzerLoaderProvider; @@ -55,9 +55,10 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; - // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the - // storage service here is well known. - _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); + // Serialization to temporary storage is only involved when we have a remote process. Which is only in VS. So the type of the + // storage service here is well known. However the serializer is created in other cases (e.g. to compute project state checksums). + // So lazily instantiate the storage service to avoid attempting to get the TemporaryStorageService when not available. + _storageService = new Lazy(() => (TemporaryStorageService)workspaceServices.GetRequiredService()); _textService = workspaceServices.GetRequiredService(); _analyzerLoaderProvider = workspaceServices.GetRequiredService(); _documentationService = workspaceServices.GetService(); @@ -275,7 +276,7 @@ public object Deserialize(WellKnownSynchronizationKind kind, ObjectReader reader WellKnownSynchronizationKind.ProjectReference => DeserializeProjectReference(reader, cancellationToken), WellKnownSynchronizationKind.MetadataReference => DeserializeMetadataReference(reader, cancellationToken), WellKnownSynchronizationKind.AnalyzerReference => DeserializeAnalyzerReference(reader, cancellationToken), - WellKnownSynchronizationKind.SerializableSourceText => SerializableSourceText.Deserialize(reader, _storageService, _textService, cancellationToken), + WellKnownSynchronizationKind.SerializableSourceText => SerializableSourceText.Deserialize(reader, _storageService.Value, _textService, cancellationToken), WellKnownSynchronizationKind.SourceGeneratorExecutionVersionMap => SourceGeneratorExecutionVersionMap.Deserialize(reader), WellKnownSynchronizationKind.FallbackAnalyzerOptions => ReadFallbackAnalyzerOptions(reader), _ => throw ExceptionUtilities.UnexpectedValue(kind), diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 15ff1bfc43d34..5d6516896f0af 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -438,7 +438,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT CopyByteArrayToStream(reader, stream, cancellationToken); var length = stream.Length; - var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); + var storageHandle = _storageService.Value.WriteToTemporaryStorage(stream, cancellationToken); Contract.ThrowIfTrue(length != storageHandle.Identifier.Size); return ReadModuleMetadataFromStorage(storageHandle); } @@ -449,7 +449,10 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. - var unmanagedStream = storageHandle.ReadFromTemporaryStorage(cancellationToken); + // + // The ITemporaryStorageStreamHandle should have given us an UnmanagedMemoryStream + // since this only runs on Windows for VS. + var unmanagedStream = (UnmanagedMemoryStream)storageHandle.ReadFromTemporaryStorage(cancellationToken); Contract.ThrowIfFalse(storageHandle.Identifier.Size == unmanagedStream.Length); // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs index e7231829c452a..45eae85bb3a62 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.Factory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -20,8 +21,18 @@ internal partial class Factory( [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) { - var textFactory = workspaceServices.GetRequiredService(); - return new TemporaryStorageService(workspaceThreadingService, textFactory); + // Only use the memory mapped file version of the temporary storage service on Windows. + // It is only required for OOP communication (Windows only) and can cause issues on Linux containers + // due to a small amount of space allocated by default to store memory mapped files. + if (PlatformInformation.IsWindows || PlatformInformation.IsRunningOnMono) + { + var textFactory = workspaceServices.GetRequiredService(); + return new TemporaryStorageService(workspaceThreadingService, textFactory); + } + else + { + return TrivialTemporaryStorageService.Instance; + } } } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs index 52d97d1bd762c..5aff9e6a84a44 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs @@ -18,7 +18,7 @@ public sealed class TemporaryStorageStreamHandle( { public TemporaryStorageIdentifier Identifier => identifier; - public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) + public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) { diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TrivialTemporaryStorageService.cs new file mode 100644 index 0000000000000..834179fc20725 --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TrivialTemporaryStorageService.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; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis; + +internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceInternal +{ + public static readonly TrivialTemporaryStorageService Instance = new(); + + private TrivialTemporaryStorageService() + { + } + + public ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + { + var newStream = new MemoryStream(); + stream.CopyTo(newStream); + return new StreamStorage(newStream); + } + + public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + { + return new TextStorage(text); + } + + public Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + { + return Task.FromResult(new TextStorage(text)); + } + + private sealed class StreamStorage : ITemporaryStorageStreamHandle + { + private readonly MemoryStream _stream; + + public TemporaryStorageIdentifier Identifier { get; } + + public StreamStorage(MemoryStream stream) + { + _stream = stream; + Identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString(), 0, _stream.Length); + } + + public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + // Return a read-only view of the underlying buffer to prevent users from overwriting or directly + // disposing the backing storage. + return new MemoryStream(_stream.GetBuffer(), 0, (int)_stream.Length, writable: false); + } + } + + private sealed class TextStorage : ITemporaryStorageTextHandle + { + private readonly SourceText _sourceText; + + public TemporaryStorageIdentifier Identifier { get; } + + public TextStorage(SourceText sourceText) + { + _sourceText = sourceText; + Identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString(), 0, _sourceText.Length); + } + + public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + return _sourceText; + } + + public Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) + { + return Task.FromResult(_sourceText); + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index a278f5a73ab30..418a8a7fe50cf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -22,5 +22,5 @@ internal interface ITemporaryStorageStreamHandle /// Reads the data indicated to by this handle into a stream. This stream can be created in a different process /// than the one that wrote the data originally. /// - UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken); + Stream ReadFromTemporaryStorage(CancellationToken cancellationToken); }