Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not use memory mapped files on non-windows #74339

Merged
merged 1 commit into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)

private readonly SolutionServices _workspaceServices;

private readonly TemporaryStorageService _storageService;
private readonly Lazy<TemporaryStorageService> _storageService;
private readonly ITextFactoryService _textService;
private readonly IDocumentationProviderService? _documentationService;
private readonly IAnalyzerAssemblyLoaderProvider _analyzerLoaderProvider;
Expand All @@ -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<ITemporaryStorageServiceInternal>();
// 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>(() => (TemporaryStorageService)workspaceServices.GetRequiredService<ITemporaryStorageServiceInternal>());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit weird. the serializer service is almost only used for oop serialization - except its also used for create project parse option checksums on all platforms.

The TemporaryStorageService is not used in that case, so instantiating it lazily to avoid throwing when it attempts to convert to TemporaryStorageService

_textService = workspaceServices.GetRequiredService<ITextFactoryService>();
_analyzerLoaderProvider = workspaceServices.GetRequiredService<IAnalyzerAssemblyLoaderProvider>();
_documentationService = workspaceServices.GetService<IDocumentationProviderService>();
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Composition;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Host;

Expand All @@ -20,8 +21,18 @@ internal partial class Factory(
[Obsolete(MefConstruction.FactoryMethodMessage, error: true)]
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
var textFactory = workspaceServices.GetRequiredService<ITextFactoryService>();
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<ITextFactoryService>();
return new TemporaryStorageService(workspaceThreadingService, textFactory);
}
else
{
return TrivialTemporaryStorageService.Instance;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
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<ITemporaryStorageTextHandle> WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken)
{
return Task.FromResult<ITemporaryStorageTextHandle>(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<SourceText> ReadFromTemporaryStorageAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_sourceText);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken);
Stream ReadFromTemporaryStorage(CancellationToken cancellationToken);
}
Loading