| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Diagnostics; |
| | | 5 | | using System.Diagnostics.CodeAnalysis; |
| | | 6 | | using System.IO; |
| | | 7 | | using System.IO.MemoryMappedFiles; |
| | | 8 | | using System.Threading; |
| | | 9 | | |
| | | 10 | | namespace System.Reflection.Internal |
| | | 11 | | { |
| | | 12 | | /// <summary> |
| | | 13 | | /// Represents data read from a stream. |
| | | 14 | | /// </summary> |
| | | 15 | | /// <remarks> |
| | | 16 | | /// Uses memory map to load data from streams backed by files that are bigger than <see cref="MemoryMapThreshold"/>. |
| | | 17 | | /// </remarks> |
| | | 18 | | internal sealed class StreamMemoryBlockProvider : MemoryBlockProvider |
| | | 19 | | { |
| | | 20 | | // We're trying to balance total VM usage (which is a minimum of 64KB for a memory mapped file) |
| | | 21 | | // with private working set (since heap memory will be backed by the paging file and non-sharable). |
| | | 22 | | // Internal for testing. |
| | | 23 | | internal const int MemoryMapThreshold = 16 * 1024; |
| | | 24 | | |
| | | 25 | | // The stream is user specified and might not be thread-safe. |
| | | 26 | | // Any read from the stream must be protected by streamGuard. |
| | | 27 | | private Stream _stream; |
| | | 28 | | private readonly object _streamGuard; |
| | | 29 | | |
| | | 30 | | private readonly bool _leaveOpen; |
| | | 31 | | private readonly bool _useMemoryMap; |
| | | 32 | | |
| | | 33 | | private readonly long _imageStart; |
| | | 34 | | private readonly int _imageSize; |
| | | 35 | | |
| | | 36 | | private MemoryMappedFile? _lazyMemoryMap; |
| | | 37 | | |
| | 0 | 38 | | public StreamMemoryBlockProvider(Stream stream, long imageStart, int imageSize, bool leaveOpen) |
| | 0 | 39 | | { |
| | 0 | 40 | | Debug.Assert(stream.CanSeek && stream.CanRead); |
| | 0 | 41 | | _stream = stream; |
| | 0 | 42 | | _streamGuard = new object(); |
| | 0 | 43 | | _imageStart = imageStart; |
| | 0 | 44 | | _imageSize = imageSize; |
| | 0 | 45 | | _leaveOpen = leaveOpen; |
| | 0 | 46 | | _useMemoryMap = stream is FileStream; |
| | 0 | 47 | | } |
| | | 48 | | |
| | | 49 | | protected override void Dispose(bool disposing) |
| | 0 | 50 | | { |
| | 0 | 51 | | Debug.Assert(disposing); |
| | 0 | 52 | | if (!_leaveOpen) |
| | 0 | 53 | | { |
| | 0 | 54 | | Interlocked.Exchange(ref _stream, null!)?.Dispose(); |
| | 0 | 55 | | } |
| | | 56 | | |
| | 0 | 57 | | Interlocked.Exchange(ref _lazyMemoryMap, null)?.Dispose(); |
| | 0 | 58 | | } |
| | | 59 | | |
| | | 60 | | public override int Size |
| | | 61 | | { |
| | | 62 | | get |
| | 0 | 63 | | { |
| | 0 | 64 | | return _imageSize; |
| | 0 | 65 | | } |
| | | 66 | | } |
| | | 67 | | |
| | | 68 | | /// <exception cref="IOException">Error reading from the stream.</exception> |
| | | 69 | | internal static unsafe NativeHeapMemoryBlock ReadMemoryBlockNoLock(Stream stream, long start, int size) |
| | 0 | 70 | | { |
| | 0 | 71 | | var block = new NativeHeapMemoryBlock(size); |
| | 0 | 72 | | bool fault = true; |
| | | 73 | | try |
| | 0 | 74 | | { |
| | 0 | 75 | | stream.Seek(start, SeekOrigin.Begin); |
| | 0 | 76 | | stream.ReadExactly(block.Pointer, size); |
| | | 77 | | |
| | 0 | 78 | | fault = false; |
| | 0 | 79 | | } |
| | | 80 | | finally |
| | 0 | 81 | | { |
| | 0 | 82 | | if (fault) |
| | 0 | 83 | | { |
| | 0 | 84 | | block.Dispose(); |
| | 0 | 85 | | } |
| | 0 | 86 | | } |
| | | 87 | | |
| | 0 | 88 | | return block; |
| | 0 | 89 | | } |
| | | 90 | | |
| | | 91 | | public override bool TryGetUnderlyingStream([NotNullWhen(true)] out Stream? stream, out long imageStart, out int |
| | 0 | 92 | | { |
| | 0 | 93 | | stream = _stream; |
| | 0 | 94 | | imageStart = _imageStart; |
| | 0 | 95 | | imageSize = _imageSize; |
| | 0 | 96 | | streamGuard = _streamGuard; |
| | 0 | 97 | | return true; |
| | 0 | 98 | | } |
| | | 99 | | |
| | | 100 | | /// <exception cref="IOException">Error while reading from the stream.</exception> |
| | | 101 | | protected override AbstractMemoryBlock GetMemoryBlockImpl(int start, int size) |
| | 0 | 102 | | { |
| | 0 | 103 | | long absoluteStart = _imageStart + start; |
| | | 104 | | |
| | 0 | 105 | | if (_useMemoryMap && size > MemoryMapThreshold) |
| | 0 | 106 | | { |
| | 0 | 107 | | return CreateMemoryMappedFileBlock(absoluteStart, size); |
| | | 108 | | } |
| | | 109 | | |
| | 0 | 110 | | lock (_streamGuard) |
| | 0 | 111 | | { |
| | 0 | 112 | | return ReadMemoryBlockNoLock(_stream!, absoluteStart, size); |
| | | 113 | | } |
| | 0 | 114 | | } |
| | | 115 | | |
| | | 116 | | /// <exception cref="IOException">IO error while mapping memory or not enough memory to create the mapping.</exc |
| | | 117 | | private MemoryMappedFileBlock CreateMemoryMappedFileBlock(long start, int size) |
| | 0 | 118 | | { |
| | 0 | 119 | | if (_lazyMemoryMap == null) |
| | 0 | 120 | | { |
| | | 121 | | // CreateMemoryMap might modify the stream (calls FileStream.Flush) |
| | 0 | 122 | | lock (_streamGuard) |
| | 0 | 123 | | { |
| | | 124 | | try |
| | 0 | 125 | | { |
| | | 126 | | // leave the underlying stream open. It will be closed by the Dispose method. |
| | 0 | 127 | | _lazyMemoryMap ??= |
| | 0 | 128 | | MemoryMappedFile.CreateFromFile( |
| | 0 | 129 | | fileStream: (FileStream)_stream, |
| | 0 | 130 | | mapName: null, |
| | 0 | 131 | | capacity: 0, |
| | 0 | 132 | | access: MemoryMappedFileAccess.Read, |
| | 0 | 133 | | inheritability: HandleInheritability.None, |
| | 0 | 134 | | leaveOpen: true); |
| | 0 | 135 | | } |
| | 0 | 136 | | catch (UnauthorizedAccessException e) |
| | 0 | 137 | | { |
| | 0 | 138 | | throw new IOException(e.Message, e); |
| | | 139 | | } |
| | 0 | 140 | | } |
| | 0 | 141 | | } |
| | | 142 | | |
| | | 143 | | MemoryMappedViewAccessor accessor; |
| | | 144 | | |
| | 0 | 145 | | lock (_streamGuard) |
| | 0 | 146 | | { |
| | 0 | 147 | | accessor = _lazyMemoryMap.CreateViewAccessor(start, size, MemoryMappedFileAccess.Read); |
| | 0 | 148 | | } |
| | | 149 | | |
| | 0 | 150 | | return new MemoryMappedFileBlock(accessor, accessor.SafeMemoryMappedViewHandle, accessor.PointerOffset, size |
| | 0 | 151 | | } |
| | | 152 | | } |
| | | 153 | | } |
| | | 154 | | |