File: Resources\MappedMemoryManager.cs
Web Access
Project: Microsoft.DotNet.Cli.CoreUtils.csproj (Microsoft.DotNet.Cli.CoreUtils)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO.MemoryMappedFiles;

namespace Microsoft.DotNet.Cli.Resources.Internal;

/// <summary>
///  A <see cref="MemoryManager{T}"/> that exposes a read-only memory-mapped view of a file as
///  <see cref="System.Memory{T}"/> / <see cref="ReadOnlyMemory{T}"/> without copying its contents.
/// </summary>
/// <remarks>
///  <para>
///   The mapped view has a fixed address for its lifetime, so <see cref="Pin"/> and <see cref="Unpin"/>
///   are trivial; <see cref="MemoryManager{T}"/> is only used to turn the native view pointer into a
///   <see cref="ReadOnlyMemory{T}"/>. The view keeps the mapping alive, so the file and mapping handles
///   are released as soon as the view exists; only the view is held for the life of this manager.
///  </para>
///  <para>
///   The backing view is mapped read-only. <see cref="MemoryManager{T}"/> requires the buffer to be
///   exposed as a writable <see cref="Span{T}"/> / <see cref="System.Memory{T}"/>, but the contents
///   must be treated as read-only: writing through the returned span or memory faults with an
///   <see cref="AccessViolationException"/> because the underlying pages are read-only. Prefer
///   consuming it as <see cref="ReadOnlyMemory{T}"/> / <see cref="ReadOnlySpan{T}"/>.
///  </para>
///  <para>
///   Retain and dispose the manager itself to unmap the view. Do not use the memory after disposing.
///  </para>
/// </remarks>
internal sealed unsafe class MappedMemoryManager : MemoryManager<byte>
{
    private readonly MappedViewLease _lease;
    private readonly byte* _pointer;
    private readonly int _length;
    private bool _disposed;

    private MappedMemoryManager(MappedViewLease lease, int length)
    {
        _lease = lease;
        _pointer = lease.Pointer;
        _length = length;
    }

    /// <summary>
    ///  Creates a <see cref="MappedMemoryManager"/> over a read-only memory mapping of the file at
    ///  <paramref name="path"/>.
    /// </summary>
    /// <param name="path">The path of the file to map.</param>
    /// <returns>A manager that owns the mapping until it is disposed.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>.</exception>
    /// <exception cref="IOException">The file is empty or larger than <see cref="int.MaxValue"/> bytes.</exception>
    public static MappedMemoryManager CreateFromFile(string path)
    {
        ArgumentNullException.ThrowIfNull(path);

        using FileStream stream = new(path, FileMode.Open, FileAccess.Read, FileShare.Read);

        long length = stream.Length;
        if (length is <= 0 or > int.MaxValue)
        {
            throw new IOException($"'{path}' is empty or too large to memory-map.");
        }

        using MemoryMappedFile file = MemoryMappedFile.CreateFromFile(
            stream,
            mapName: null,
            capacity: 0,
            MemoryMappedFileAccess.Read,
            HandleInheritability.None,
            leaveOpen: true);

        MemoryMappedViewAccessor accessor = file.CreateViewAccessor(0, length, MemoryMappedFileAccess.Read);
        MappedViewLease lease = new(accessor);
        return new MappedMemoryManager(lease, (int)length);
    }

    /// <inheritdoc/>
    public override Span<byte> GetSpan()
    {
        ObjectDisposedException.ThrowIf(_disposed, this);
        return new(_pointer, _length);
    }

    /// <inheritdoc/>
    public override MemoryHandle Pin(int elementIndex = 0)
    {
        ObjectDisposedException.ThrowIf(_disposed, this);
        ArgumentOutOfRangeException.ThrowIfNegative(elementIndex);
        ArgumentOutOfRangeException.ThrowIfGreaterThan(elementIndex, _length);
        return new(_pointer + elementIndex, handle: default, this);
    }

    /// <inheritdoc/>
    public override void Unpin()
    {
    }

    /// <inheritdoc/>
    protected override void Dispose(bool disposing)
    {
        if (_disposed)
        {
            return;
        }

        _disposed = true;
        _lease.Dispose();
    }
}