File: System\Reflection\Internal\MemoryBlocks\StreamMemoryBlockProvider.cs
Web Access
Project: src\src\libraries\System.Reflection.Metadata\src\System.Reflection.Metadata.csproj (System.Reflection.Metadata)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
 
namespace System.Reflection.Internal
{
    /// <summary>
    /// Represents data read from a stream.
    /// </summary>
    /// <remarks>
    /// Uses memory map to load data from streams backed by files that are bigger than <see cref="MemoryMapThreshold"/>.
    /// </remarks>
    internal sealed class StreamMemoryBlockProvider : MemoryBlockProvider
    {
        // We're trying to balance total VM usage (which is a minimum of 64KB for a memory mapped file)
        // with private working set (since heap memory will be backed by the paging file and non-sharable).
        // Internal for testing.
        internal const int MemoryMapThreshold = 16 * 1024;
 
        // The stream is user specified and might not be thread-safe.
        // Any read from the stream must be protected by streamGuard.
        private Stream _stream;
        private readonly object _streamGuard;
 
        private readonly bool _leaveOpen;
        private bool _useMemoryMap;
 
        private readonly long _imageStart;
        private readonly int _imageSize;
 
        private MemoryMappedFile? _lazyMemoryMap;
 
        public StreamMemoryBlockProvider(Stream stream, long imageStart, int imageSize, bool leaveOpen)
        {
            Debug.Assert(stream.CanSeek && stream.CanRead);
            _stream = stream;
            _streamGuard = new object();
            _imageStart = imageStart;
            _imageSize = imageSize;
            _leaveOpen = leaveOpen;
            _useMemoryMap = stream is FileStream;
        }
 
        protected override void Dispose(bool disposing)
        {
            Debug.Assert(disposing);
            if (!_leaveOpen)
            {
                Interlocked.Exchange(ref _stream, null!)?.Dispose();
            }
 
            Interlocked.Exchange(ref _lazyMemoryMap, null)?.Dispose();
        }
 
        public override int Size
        {
            get
            {
                return _imageSize;
            }
        }
 
        /// <exception cref="IOException">Error reading from the stream.</exception>
        internal static unsafe NativeHeapMemoryBlock ReadMemoryBlockNoLock(Stream stream, long start, int size)
        {
            var block = new NativeHeapMemoryBlock(size);
            bool fault = true;
            try
            {
                stream.Seek(start, SeekOrigin.Begin);
 
                int bytesRead = 0;
 
                if ((bytesRead = stream.Read(block.Pointer, size)) != size)
                {
                    stream.CopyTo(block.Pointer + bytesRead, size - bytesRead);
                }
 
                fault = false;
            }
            finally
            {
                if (fault)
                {
                    block.Dispose();
                }
            }
 
            return block;
        }
 
        /// <exception cref="IOException">Error while reading from the stream.</exception>
        protected override AbstractMemoryBlock GetMemoryBlockImpl(int start, int size)
        {
            long absoluteStart = _imageStart + start;
 
            if (_useMemoryMap && size > MemoryMapThreshold)
            {
                if (TryCreateMemoryMappedFileBlock(absoluteStart, size, out MemoryMappedFileBlock? block))
                {
                    return block;
                }
 
                _useMemoryMap = false;
            }
 
            lock (_streamGuard)
            {
                return ReadMemoryBlockNoLock(_stream!, absoluteStart, size);
            }
        }
 
        public override Stream GetStream(out StreamConstraints constraints)
        {
            constraints = new StreamConstraints(_streamGuard, _imageStart, _imageSize);
            return _stream;
        }
 
        /// <exception cref="IOException">IO error while mapping memory or not enough memory to create the mapping.</exception>
        private unsafe bool TryCreateMemoryMappedFileBlock(long start, int size, [NotNullWhen(true)] out MemoryMappedFileBlock? block)
        {
            if (_lazyMemoryMap == null)
            {
                // leave the underlying stream open. It will be closed by the Dispose method.
                MemoryMappedFile newMemoryMap;
 
                // CreateMemoryMap might modify the stream (calls FileStream.Flush)
                lock (_streamGuard)
                {
                    try
                    {
                        newMemoryMap =
                            MemoryMappedFile.CreateFromFile(
                                fileStream: (FileStream)_stream,
                                mapName: null,
                                capacity: 0,
                                access: MemoryMappedFileAccess.Read,
                                inheritability: HandleInheritability.None,
                                leaveOpen: true);
                    }
                    catch (UnauthorizedAccessException e)
                    {
                        throw new IOException(e.Message, e);
                    }
                }
 
                if (newMemoryMap == null)
                {
                    block = null;
                    return false;
                }
 
                if (Interlocked.CompareExchange(ref _lazyMemoryMap, newMemoryMap, null) != null)
                {
                    newMemoryMap.Dispose();
                }
            }
 
            MemoryMappedViewAccessor accessor;
 
            lock (_streamGuard)
            {
                accessor = _lazyMemoryMap.CreateViewAccessor(start, size, MemoryMappedFileAccess.Read);
            }
 
            if (accessor == null)
            {
                block = null;
                return false;
            }
 
            block = new MemoryMappedFileBlock(accessor, accessor.SafeMemoryMappedViewHandle, accessor.PointerOffset, size);
            return true;
        }
    }
}