TemporaryStorageServiceFactory.MemoryMappedInfo.cs 14.3 KB
Newer Older
1
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
P
Pilchie 已提交
2 3

using System;
4
using System.Diagnostics;
P
Pilchie 已提交
5 6
using System.IO;
using System.IO.MemoryMappedFiles;
7
using System.Runtime;
8
using System.Runtime.InteropServices;
P
Pilchie 已提交
9 10 11 12 13 14 15
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Host
{
    internal partial class TemporaryStorageServiceFactory
    {
        /// <summary>
16 17
        /// Our own abstraction on top of memory map file so that we can have shared views over mmf files. 
        /// Otherwise, each view has minimum size of 64K due to requirement forced by windows.
P
Pilchie 已提交
18
        /// 
19 20
        /// most of our view will have short lifetime, but there are cases where view might live a bit longer such as
        /// metadata dll shadow copy. shared view will help those cases.
P
Pilchie 已提交
21
        /// </summary>
22 23 24 25 26 27 28 29 30 31 32 33 34
        /// <remarks>
        /// <para>Instances of this class should be disposed when they are no longer needed. After disposing this
        /// instance, it should no longer be used. However, streams obtained through <see cref="CreateReadableStream"/>
        /// or <see cref="CreateWritableStream"/> will not be invalidated until they are disposed independently (which
        /// may occur before or after the <see cref="MemoryMappedInfo"/> is disposed.</para>
        ///
        /// <para>This class and its nested types have familiar APIs and predictable behavior when used in other code,
        /// but are non-trivial to work on. The implementations of <see cref="IDisposable"/> adhere to the best
        /// practices described in
        /// <see href="http://joeduffyblog.com/2005/04/08/dg-update-dispose-finalization-and-resource-management/">DG
        /// Update: Dispose, Finalization, and Resource Management</see>. Additional notes regarding operating system
        /// behavior leveraged for efficiency are given in comments.</para>
        /// </remarks>
P
Pilchie 已提交
35 36 37
        internal sealed class MemoryMappedInfo : IDisposable
        {
            /// <summary>
38
            /// The memory mapped file.
P
Pilchie 已提交
39
            /// </summary>
40 41 42 43 44
            /// <remarks>
            /// <para>It is possible for this accessor to be disposed prior to the view and/or the streams which use it.
            /// However, the operating system does not actually close the views which are in use until the view handles
            /// are closed as well, even if the <see cref="MemoryMappedFile"/> is disposed first.</para>
            /// </remarks>
45
            private readonly ReferenceCountedDisposable<MemoryMappedFile> _memoryMappedFile;
P
Pilchie 已提交
46 47

            /// <summary>
48
            /// A weak reference to a read-only view for the memory mapped file.
P
Pilchie 已提交
49
            /// </summary>
50
            /// <remarks>
51 52 53
            /// <para>This holds a weak counted reference to current <see cref="MemoryMappedViewAccessor"/>, which
            /// allows additional accessors for the same address space to be obtained up until the point when no
            /// external code is using it. When the memory is no longer being used by any
54 55 56 57 58
            /// <see cref="SharedReadableStream"/> objects, the view of the memory mapped file is unmapped, making the
            /// process address space it previously claimed available for other purposes. If/when it is needed again, a
            /// new view is created.</para>
            ///
            /// <para>This view is read-only, so it is only used by <see cref="CreateReadableStream"/>.</para>
59
            /// </remarks>
60
            private ReferenceCountedDisposable<MemoryMappedViewAccessor>.WeakReference _weakReadAccessor;
P
Pilchie 已提交
61

62
            public MemoryMappedInfo(ReferenceCountedDisposable<MemoryMappedFile> memoryMappedFile, string name, long offset, long size)
63
            {
64
                _memoryMappedFile = memoryMappedFile;
65 66 67
                Name = name;
                Offset = offset;
                Size = size;
68
            }
P
Pilchie 已提交
69

70
            public MemoryMappedInfo(string name, long offset, long size)
71
                : this(new ReferenceCountedDisposable<MemoryMappedFile>(MemoryMappedFile.OpenExisting(name)), name, offset, size)
P
Pilchie 已提交
72 73 74
            {
            }

75
            /// <summary>
76 77 78 79 80 81 82 83 84 85 86 87 88
            /// The name of the memory mapped file.
            /// </summary>
            public string Name { get; }

            /// <summary>
            /// The offset into the memory mapped file of the region described by the current
            /// <see cref="MemoryMappedInfo"/>.
            /// </summary>
            public long Offset { get; }

            /// <summary>
            /// The size of the region of the memory mapped file described by the current
            /// <see cref="MemoryMappedInfo"/>.
89
            /// </summary>
90
            public long Size { get; }
91

92
            private static void ForceCompactingGC()
93 94
            {
                // repeated GC.Collect / WaitForPendingFinalizers till memory freed delta is super small, ignore the return value
H
Heejae Chang 已提交
95
                GC.GetTotalMemory(forceFullCollection: true);
96 97 98 99 100 101

                // compact the LOH
                GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
                GC.Collect();
            }

P
Pilchie 已提交
102 103 104 105 106 107
            /// <summary>
            /// Caller is responsible for disposing the returned stream.
            /// multiple call of this will not increase VM.
            /// </summary>
            public Stream CreateReadableStream()
            {
108
                // CreateViewAccessor is not guaranteed to be thread-safe
109
                lock (_memoryMappedFile.Target)
P
Pilchie 已提交
110
                {
111 112 113
                    // Note: TryAddReference behaves according to its documentation even if the target object has been
                    // disposed. If it returns non-null, then the object will not be disposed before the returned
                    // reference is disposed (see comments on _memoryMappedFile and TryAddReference).
114
                    var streamAccessor = _weakReadAccessor.TryAddReference();
115
                    if (streamAccessor == null)
P
Pilchie 已提交
116
                    {
117
                        var rawAccessor = RunWithCompactingGCFallback(info => info._memoryMappedFile.Target.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read), this);
118
                        streamAccessor = new ReferenceCountedDisposable<MemoryMappedViewAccessor>(rawAccessor);
119
                        _weakReadAccessor = new ReferenceCountedDisposable<MemoryMappedViewAccessor>.WeakReference(streamAccessor);
P
Pilchie 已提交
120 121
                    }

122
                    Debug.Assert(streamAccessor.Target.CanRead);
123
                    return new SharedReadableStream(this, streamAccessor, Size);
P
Pilchie 已提交
124 125 126 127 128 129 130 131 132 133
                }
            }

            /// <summary>
            /// Caller is responsible for disposing the returned stream.
            /// multiple call of this will increase VM.
            /// </summary>
            public Stream CreateWritableStream()
            {
                // CreateViewStream is not guaranteed to be thread-safe
134
                lock (_memoryMappedFile.Target)
P
Pilchie 已提交
135
                {
136
                    return RunWithCompactingGCFallback(info => info._memoryMappedFile.Target.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write), this);
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
                }
            }

            /// <summary>
            /// Run a function which may fail with an <see cref="IOException"/> if not enough memory is available to
            /// satisfy the request. In this case, a full compacting GC pass is forced and the function is attempted
            /// again.
            /// </summary>
            /// <remarks>
            /// <para><see cref="MemoryMappedFile.CreateViewAccessor(long, long, MemoryMappedFileAccess)"/> and
            /// <see cref="MemoryMappedFile.CreateViewStream(long, long, MemoryMappedFileAccess)"/> will use a native
            /// memory map, which can't trigger a GC. In this case, we'd otherwise crash with OOM, so we don't care
            /// about creating a UI delay with a full forced compacting GC. If it crashes the second try, it means we're
            /// legitimately out of resources.</para>
            /// </remarks>
            /// <typeparam name="TArg">The type of argument to pass to the callback.</typeparam>
            /// <typeparam name="T">The type returned by the function.</typeparam>
            /// <param name="function">The function to execute.</param>
            /// <param name="argument">The argument to pass to the function.</param>
            /// <returns>The value returned by <paramref name="function"/>.</returns>
            private static T RunWithCompactingGCFallback<TArg, T>(Func<TArg, T> function, TArg argument)
            {
                try
                {
                    return function(argument);
                }
                catch (IOException)
                {
                    ForceCompactingGC();
                    return function(argument);
P
Pilchie 已提交
167 168 169 170 171
                }
            }

            public void Dispose()
            {
172 173 174
                // See remarks on field for relation between _memoryMappedFile and the views/streams. There is no
                // need to write _weakReadAccessor here since lifetime of the target is not owned by this instance.
                _memoryMappedFile.Dispose();
175 176
            }

177
            private unsafe sealed class SharedReadableStream : Stream, ISupportDirectMemoryAccess
P
Pilchie 已提交
178
            {
179
                private readonly ReferenceCountedDisposable<MemoryMappedViewAccessor> _accessor;
P
Pilchie 已提交
180

181 182 183
                private byte* _start;
                private byte* _current;
                private readonly byte* _end;
P
Pilchie 已提交
184

185
                public SharedReadableStream(MemoryMappedInfo owner, ReferenceCountedDisposable<MemoryMappedViewAccessor> accessor, long length)
P
Pilchie 已提交
186
                {
187 188
                    _accessor = accessor;
                    _current = _start = (byte*)_accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + _accessor.Target.PointerOffset;
189
                    _end = checked(_start + length);
P
Pilchie 已提交
190 191 192 193 194 195
                }

                public override bool CanRead
                {
                    get
                    {
196
                        return true;
P
Pilchie 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
                    }
                }

                public override bool CanSeek
                {
                    get
                    {
                        return true;
                    }
                }

                public override bool CanWrite
                {
                    get
                    {
                        return false;
                    }
                }

                public override long Length
                {
                    get
                    {
220
                        return _end - _start;
P
Pilchie 已提交
221 222 223 224 225 226 227
                    }
                }

                public override long Position
                {
                    get
                    {
228
                        return _current - _start;
P
Pilchie 已提交
229 230 231 232
                    }

                    set
                    {
233 234
                        var target = _start + value;
                        if (target < _start || target >= _end)
P
Pilchie 已提交
235
                        {
236
                            throw new ArgumentOutOfRangeException(nameof(value));
P
Pilchie 已提交
237 238
                        }

239
                        _current = target;
P
Pilchie 已提交
240 241 242 243 244
                    }
                }

                public override int ReadByte()
                {
245
                    // PERF: Keeping this as simple as possible since it's on the hot path
246
                    if (_current >= _end)
P
Pilchie 已提交
247 248 249 250
                    {
                        return -1;
                    }

251
                    return *_current++;
P
Pilchie 已提交
252 253 254 255
                }

                public override int Read(byte[] buffer, int offset, int count)
                {
256
                    if (_current >= _end)
P
Pilchie 已提交
257 258 259 260
                    {
                        return 0;
                    }

C
Use var  
Cyrus Najmabadi 已提交
261
                    var adjustedCount = Math.Min(count, (int)(_end - _current));
262
                    Marshal.Copy((IntPtr)_current, buffer, offset, adjustedCount);
P
Pilchie 已提交
263

264
                    _current += adjustedCount;
265
                    return adjustedCount;
P
Pilchie 已提交
266 267 268 269
                }

                public override long Seek(long offset, SeekOrigin origin)
                {
270
                    byte* target;
P
Pilchie 已提交
271 272 273 274 275
                    try
                    {
                        switch (origin)
                        {
                            case SeekOrigin.Begin:
276
                                target = checked(_start + offset);
P
Pilchie 已提交
277 278 279
                                break;

                            case SeekOrigin.Current:
280
                                target = checked(_current + offset);
P
Pilchie 已提交
281 282 283
                                break;

                            case SeekOrigin.End:
284
                                target = checked(_end + offset);
P
Pilchie 已提交
285 286 287
                                break;

                            default:
288
                                throw new ArgumentOutOfRangeException(nameof(origin));
P
Pilchie 已提交
289 290 291 292
                        }
                    }
                    catch (OverflowException)
                    {
293
                        throw new ArgumentOutOfRangeException(nameof(offset));
P
Pilchie 已提交
294 295
                    }

296
                    if (target < _start || target >= _end)
P
Pilchie 已提交
297
                    {
298
                        throw new ArgumentOutOfRangeException(nameof(offset));
P
Pilchie 已提交
299 300
                    }

301 302
                    _current = target;
                    return _current - _start;
P
Pilchie 已提交
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
                }

                public override void Flush()
                {
                    throw new NotSupportedException();
                }

                public override void SetLength(long value)
                {
                    throw new NotSupportedException();
                }

                public override void Write(byte[] buffer, int offset, int count)
                {
                    throw new NotSupportedException();
                }

                protected override void Dispose(bool disposing)
                {
                    base.Dispose(disposing);

324
                    if (disposing)
P
Pilchie 已提交
325
                    {
326
                        _accessor.Dispose();
P
Pilchie 已提交
327 328
                    }

329
                    _start = null;
P
Pilchie 已提交
330 331 332
                }

                /// <summary>
333
                /// Get underlying native memory directly.
P
Pilchie 已提交
334 335 336
                /// </summary>
                public IntPtr GetPointer()
                {
337
                    return (IntPtr)_start;
P
Pilchie 已提交
338 339 340 341 342
                }
            }
        }
    }
}