提交 354a3686 编写于 作者: S Sam Harwell 提交者: GitHub

Merge pull request #19624 from sharwell/gc-fallback

Smarter resource management in MemoryMappedInfo
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.CodeAnalysis.Host
{
internal partial class TemporaryStorageServiceFactory
{
/// <summary>
/// This critical handle increments the reference count of a <see cref="SafeMemoryMappedViewHandle"/>. The view
/// will be released when it is disposed <em>and</em> all pointers acquired through
/// <see cref="SafeBuffer.AcquirePointer"/> (which this class uses) are released.
/// </summary>
/// <remarks>
/// <para><see cref="CriticalHandle"/> types are not reference counted, and are thus somewhat limited in their
/// usefulness. However, this handle class has tightly restricted accessibility and is only used by managed
/// code which does not rely on it counting references.</para>
///
/// <para>This is a supporting class for <see cref="MemoryMappedInfo"/>. See additional comments on that
/// class.</para>
/// </remarks>
private unsafe sealed class CopiedMemoryMappedViewHandle : CriticalHandleZeroOrMinusOneIsInvalid
{
private readonly SafeMemoryMappedViewHandle _viewHandle;
public CopiedMemoryMappedViewHandle(SafeMemoryMappedViewHandle viewHandle)
{
_viewHandle = viewHandle;
byte* pointer = null;
// The following code uses a constrained execution region (CER) to ensure that the code ends up in one
// of the following states, even in the presence of asynchronous exceptions like ThreadAbortException:
//
// 1. The pointer is not acquired, and the current handle is invalid (thus will not be released)
// 2. The pointer is acquired, and the current handle is fully initialized for later cleanup
RuntimeHelpers.PrepareConstrainedRegions();
try
{
viewHandle.AcquirePointer(ref pointer);
}
finally
{
SetHandle((IntPtr)pointer);
}
}
public byte* Pointer => (byte*)handle;
protected override bool ReleaseHandle()
{
// The operating system will release these handles when the process terminates, so do not spend time
// releasing them manually in that case. Doing so causes unnecessary delays during application shutdown.
if (!Environment.HasShutdownStarted)
{
_viewHandle.ReleasePointer();
}
return true;
}
}
}
}
......@@ -33,9 +33,6 @@ internal partial class TemporaryStorageServiceFactory
/// </remarks>
internal sealed class MemoryMappedInfo : IDisposable
{
private readonly string _name;
private readonly long _size;
/// <summary>
/// The memory mapped file.
/// </summary>
......@@ -44,43 +41,54 @@ internal sealed class MemoryMappedInfo : IDisposable
/// 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>
private readonly MemoryMappedFile _memoryMappedFile;
private readonly ReferenceCountedDisposable<MemoryMappedFile> _memoryMappedFile;
/// <summary>
/// actual memory accessor that owns the VM
/// A weak reference to a read-only view for the memory mapped file.
/// </summary>
/// <remarks>
/// <para>It is possible for this accessor to be disposed prior to the streams which use it. However, the
/// streams interact directly with the underlying memory buffer, and keep a
/// <see cref="CopiedMemoryMappedViewHandle"/> to prevent that buffer from being released while still in
/// use. The <see cref="SafeHandle"/> used by this accessor is reference counted, and is not finally
/// released until the reference count reaches zero.</para>
/// <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
/// <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>
/// </remarks>
private MemoryMappedViewAccessor _accessor;
private ReferenceCountedDisposable<MemoryMappedViewAccessor>.WeakReference _weakReadAccessor;
public MemoryMappedInfo(long size)
public MemoryMappedInfo(ReferenceCountedDisposable<MemoryMappedFile> memoryMappedFile, string name, long offset, long size)
{
_name = CreateUniqueName(size);
_size = size;
_memoryMappedFile = MemoryMappedFile.CreateNew(_name, size);
_memoryMappedFile = memoryMappedFile;
Name = name;
Offset = offset;
Size = size;
}
public MemoryMappedInfo(string name, long size)
public MemoryMappedInfo(string name, long offset, long size)
: this(new ReferenceCountedDisposable<MemoryMappedFile>(MemoryMappedFile.OpenExisting(name)), name, offset, size)
{
_name = name;
_size = size;
_memoryMappedFile = MemoryMappedFile.OpenExisting(_name);
}
/// <summary>
/// Name and Size of memory map file
/// 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"/>.
/// </summary>
public string Name => _name;
public long Size => _size;
public long Size { get; }
private void ForceCompactingGC()
private static void ForceCompactingGC()
{
// repeated GC.Collect / WaitForPendingFinalizers till memory freed delta is super small, ignore the return value
GC.GetTotalMemory(forceFullCollection: true);
......@@ -96,27 +104,22 @@ private void ForceCompactingGC()
/// </summary>
public Stream CreateReadableStream()
{
// CreateViewStream is not guaranteed to be thread-safe
lock (_memoryMappedFile)
// CreateViewAccessor is not guaranteed to be thread-safe
lock (_memoryMappedFile.Target)
{
if (_accessor == null)
// 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).
var streamAccessor = _weakReadAccessor.TryAddReference();
if (streamAccessor == null)
{
try
{
_accessor = _memoryMappedFile.CreateViewAccessor(0, _size, MemoryMappedFileAccess.Read);
}
catch (IOException)
{
// CreateViewAccessor 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.
this.ForceCompactingGC();
_accessor = _memoryMappedFile.CreateViewAccessor(0, _size, MemoryMappedFileAccess.Read);
}
var rawAccessor = RunWithCompactingGCFallback(info => info._memoryMappedFile.Target.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read), this);
streamAccessor = new ReferenceCountedDisposable<MemoryMappedViewAccessor>(rawAccessor);
_weakReadAccessor = new ReferenceCountedDisposable<MemoryMappedViewAccessor>.WeakReference(streamAccessor);
}
Contract.Assert(_accessor.CanRead);
return new SharedReadableStream(this, new CopiedMemoryMappedViewHandle(_accessor.SafeMemoryMappedViewHandle), _accessor.PointerOffset, _size);
Contract.Assert(streamAccessor.Target.CanRead);
return new SharedReadableStream(this, streamAccessor, Size);
}
}
......@@ -127,9 +130,39 @@ public Stream CreateReadableStream()
public Stream CreateWritableStream()
{
// CreateViewStream is not guaranteed to be thread-safe
lock (_memoryMappedFile)
lock (_memoryMappedFile.Target)
{
return _memoryMappedFile.CreateViewStream(0, _size, MemoryMappedFileAccess.Write);
return RunWithCompactingGCFallback(info => info._memoryMappedFile.Target.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write), this);
}
}
/// <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);
}
}
......@@ -143,38 +176,24 @@ private void Dispose(bool disposing)
{
if (disposing)
{
lock (_memoryMappedFile)
{
if (_accessor != null)
{
// (see remarks on accessor for relation between _accessor and the streams)
_accessor.Dispose();
_accessor = null;
}
}
// (see remarks on accessor for relation between _memoryMappedFile and the views/streams)
// 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();
}
}
public static string CreateUniqueName(long size)
{
return "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N");
}
private unsafe sealed class SharedReadableStream : Stream, ISupportDirectMemoryAccess
{
private readonly CopiedMemoryMappedViewHandle _handle;
private readonly ReferenceCountedDisposable<MemoryMappedViewAccessor> _accessor;
private byte* _start;
private byte* _current;
private readonly byte* _end;
public SharedReadableStream(MemoryMappedInfo owner, CopiedMemoryMappedViewHandle handle, long offset, long length)
public SharedReadableStream(MemoryMappedInfo owner, ReferenceCountedDisposable<MemoryMappedViewAccessor> accessor, long length)
{
_handle = handle;
_current = _start = handle.Pointer + offset;
_accessor = accessor;
_current = _start = (byte*)_accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + _accessor.Target.PointerOffset;
_end = checked(_start + length);
}
......@@ -312,7 +331,7 @@ protected override void Dispose(bool disposing)
if (disposing)
{
_handle.Dispose();
_accessor.Dispose();
}
_start = null;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.CompilerServices;
using System.Threading;
#if DEBUG
using System.Diagnostics;
#endif
namespace Microsoft.CodeAnalysis.Host
{
internal partial class TemporaryStorageServiceFactory
{
/// <summary>
/// A reference-counting wrapper which allows multiple uses of a single disposable object in code, which is
/// deterministically released (by calling <see cref="IDisposable.Dispose"/>) when the last reference is
/// disposed.
/// </summary>
/// <remarks>
/// <para>Each instance of <see cref="ReferenceCountedDisposable{T}"/> represents a counted reference (also
/// referred to as a <em>reference</em> in the following documentation) to a target object. Each of these
/// references has a lifetime, starting when it is constructed and continuing through its release. During
/// this time, the reference is considered <em>alive</em>. Each reference which is alive owns exactly one
/// reference to the target object, ensuring that it will not be disposed while still in use. A reference is
/// released through either of the following actions:</para>
///
/// <list type="bullet">
/// <item>The reference is explicitly released by a call to <see cref="Dispose"/>.</item>
/// <item>The reference is no longer in use by managed code and gets reclaimed by the garbage collector.</item>
/// </list>
///
/// <para>While each instance of <see cref="ReferenceCountedDisposable{T}"/> should be explicitly disposed when
/// the object is no longer needed by the code owning the reference, this implementation will not leak resources
/// in the event one or more callers fail to do so. When all references to an object are explicitly released
/// (i.e. by calling <see cref="Dispose"/>), the target object will itself be deterministically released by a
/// call to <see cref="IDisposable.Dispose"/> when the last reference to it is released. However, in the event
/// one or more references is not explicitly released, the underlying object will still become eligible for
/// non-deterministic release (i.e. finalization) as soon as each reference to it is released by one of the
/// two actions described previously.</para>
///
/// <para>When using <see cref="ReferenceCountedDisposable{T}"/>, certain steps must be taken to ensure the
/// target object is not disposed early.</para>
///
/// <list type="number">
/// <para>Use <see cref="ReferenceCountedDisposable{T}"/> consistently. In other words, do not mix code using
/// reference-counted wrappers with code that references to the target directly.</para>
/// <para>Only use the <see cref="ReferenceCountedDisposable{T}(T)"/> constructor one time per target object.
/// Additional references to the same target object must only be obtained by calling
/// <see cref="TryAddReference"/>.</para>
/// <para>Do not call <see cref="IDisposable.Dispose"/> on the target object directly. It will be called
/// automatically at the appropriate time, as described above.</para>
/// </list>
///
/// <para>All public methods on this type adhere to their pre- and post-conditions and will not invalidate state
/// even in concurrent execution.</para>
/// </remarks>
/// <typeparam name="T">The type of disposable object.</typeparam>
internal sealed class ReferenceCountedDisposable<T> : IDisposable
where T : class, IDisposable
{
/// <summary>
/// The target of this reference. This value is initialized to a non-<see langword="null"/> value in the
/// constructor, and set to <see langword="null"/> when the current reference is disposed.
/// </summary>
/// <remarks>
/// <para>This value is only cleared in order to support cases where one or more references is garbage
/// collected without having <see cref="Dispose"/> called.</para>
/// </remarks>
private T _instance;
/// <summary>
/// The boxed reference count, which is shared by all references with the same <see cref="Target"/> object.
/// </summary>
/// <remarks>
/// <para>This field serves as the synchronization object for the current type, since it is shared among all
/// counted reference to the same target object. Accesses to <see cref="StrongBox{T}.Value"/> should only
/// occur when this object is locked.</para>
///
/// <para>PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is
/// available in source control history. The use of exclusive locks was not causing any measurable
/// performance overhead even on 28-thread machines at the time this was written.</para>
/// </remarks>
private readonly StrongBox<int> _boxedReferenceCount;
/// <summary>
/// Initializes a new reference counting wrapper around an <see cref="IDisposable"/> object.
/// </summary>
/// <remarks>
/// <para>The reference count is initialized to 1.</para>
/// </remarks>
/// <param name="instance">The object owned by this wrapper.</param>
/// <exception cref="ArgumentNullException">
/// If <paramref name="instance"/> is <see langword="null"/>.
/// </exception>
public ReferenceCountedDisposable(T instance)
: this(instance, new StrongBox<int>(1))
{
}
private ReferenceCountedDisposable(T instance, StrongBox<int> referenceCount)
{
_instance = instance ?? throw new ArgumentNullException(nameof(instance));
// The reference count has already been incremented for this instance
_boxedReferenceCount = referenceCount;
}
/// <summary>
/// Gets the target object.
/// </summary>
/// <remarks>
/// <para>This call is not valid after <see cref="Dispose"/> is called. If this property or the target
/// object is used concurrently with a call to <see cref="Dispose"/>, it is possible for the code to be
/// using a disposed object. After the current instance is disposed, this property throws
/// <see cref="ObjectDisposedException"/>. However, the exact time when this property starts throwing after
/// <see cref="Dispose"/> is called is unspecified; code is expected to not use this property or the object
/// it returns after any code invokes <see cref="Dispose"/>.</para>
/// </remarks>
/// <value>The target object.</value>
public T Target => _instance ?? throw new ObjectDisposedException(nameof(ReferenceCountedDisposable<T>));
/// <summary>
/// Increments the reference count for the disposable object, and returns a new disposable reference to it.
/// </summary>
/// <remarks>
/// <para>The returned object is an independent reference to the same underlying object. Disposing of the
/// returned value multiple times will only cause the reference count to be decreased once.</para>
/// </remarks>
/// <returns>A new <see cref="ReferenceCountedDisposable{T}"/> pointing to the same underlying object, if it
/// has not yet been disposed; otherwise, <see langword="null"/> if this reference to the underlying object
/// has already been disposed.</returns>
public ReferenceCountedDisposable<T> TryAddReference()
{
return TryAddReferenceImpl(_instance, _boxedReferenceCount);
}
/// <summary>
/// Provides the implementation for <see cref="TryAddReference"/> and
/// <see cref="WeakReference.TryAddReference"/>.
/// </summary>
private static ReferenceCountedDisposable<T> TryAddReferenceImpl(T target, StrongBox<int> referenceCount)
{
lock (referenceCount)
{
if (referenceCount.Value == 0)
{
// The target is already disposed, and cannot be reused
return null;
}
if (target == null)
{
// The current reference has been disposed, so even though it isn't disposed yet we don't have a
// reference to the target
return null;
}
checked
{
referenceCount.Value++;
}
// Must return a new instance, in order for the Dispose operation on each individual instance to
// be idempotent.
return new ReferenceCountedDisposable<T>(target, referenceCount);
}
}
/// <summary>
/// Releases the current reference, causing the underlying object to be disposed if this was the last
/// reference.
/// </summary>
/// <remarks>
/// <para>After this instance is disposed, the <see cref="TryAddReference"/> method can no longer be used to
/// object a new reference to the target, even if other references to the target object are still in
/// use.</para>
/// </remarks>
public void Dispose()
{
T instanceToDispose = null;
lock (_boxedReferenceCount)
{
if (_instance == null)
{
// Already disposed; allow multiple without error.
return;
}
_boxedReferenceCount.Value--;
if (_boxedReferenceCount.Value == 0)
{
instanceToDispose = _instance;
}
// Ensure multiple calls to Dispose for this instance are a NOP.
_instance = null;
}
instanceToDispose?.Dispose();
}
/// <summary>
/// Represents a weak reference to a <see cref="ReferenceCountedDisposable{T}"/> which is capable of
/// obtaining a new counted reference up until the point when the object is no longer accessible.
/// </summary>
public struct WeakReference
{
/// <summary>
/// DO NOT DISPOSE OF THE TARGET.
/// </summary>
private readonly WeakReference<T> _weakInstance;
private readonly StrongBox<int> _boxedReferenceCount;
public WeakReference(ReferenceCountedDisposable<T> reference)
: this()
{
if (reference == null)
{
throw new ArgumentNullException(nameof(reference));
}
var instance = reference._instance;
var referenceCount = reference._boxedReferenceCount;
if (instance == null)
{
// The specified reference is already not valid. This case is supported by WeakReference (not
// unlike `new System.WeakReference(null)`), but we return early to avoid an unnecessary
// allocation in this case.
return;
}
_weakInstance = new WeakReference<T>(instance);
_boxedReferenceCount = referenceCount;
}
/// <summary>
/// Increments the reference count for the disposable object, and returns a new disposable reference to
/// it.
/// </summary>
/// <remarks>
/// <para>Unlike <see cref="ReferenceCountedDisposable{T}.TryAddReference"/>, this method is capable of
/// adding a reference to the underlying instance all the way up to the point where it is finally
/// disposed.</para>
///
/// <para>The returned object is an independent reference to the same underlying object. Disposing of
/// the returned value multiple times will only cause the reference count to be decreased once.</para>
/// </remarks>
/// <returns>A new <see cref="ReferenceCountedDisposable{T}"/> pointing to the same underlying object,
/// if it has not yet been disposed; otherwise, <see langword="null"/> if the underlying object has
/// already been disposed.</returns>
public ReferenceCountedDisposable<T> TryAddReference()
{
var weakInstance = _weakInstance;
if (weakInstance == null || !_weakInstance.TryGetTarget(out var target))
{
return null;
}
var referenceCount = _boxedReferenceCount;
if (referenceCount == null)
{
return null;
}
return TryAddReferenceImpl(target, referenceCount);
}
}
}
}
}
......@@ -4,6 +4,7 @@
using System.Composition;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
......@@ -30,8 +31,72 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
/// </summary>
internal class TemporaryStorageService : ITemporaryStorageService2
{
/// <summary>
/// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other
/// storage units.
/// </summary>
/// <remarks>
/// <para>This value was arbitrarily chosen and appears to work well. Can be changed if data suggests
/// something better.</para>
/// </remarks>
/// <seealso cref="_weakFileReference"/>
private const long SingleFileThreshold = 128 * 1024;
/// <summary>
/// The size in bytes of a memory mapped file created to store multiple temporary objects.
/// </summary>
/// <remarks>
/// <para>This value was arbitrarily chosen and appears to work well. Can be changed if data suggests
/// something better.</para>
/// </remarks>
/// <seealso cref="_weakFileReference"/>
private const long MultiFileBlockSize = SingleFileThreshold * 32;
private readonly ITextFactoryService _textFactory;
/// <summary>
/// The synchronization object for accessing the memory mapped file related fields (indicated in the remarks
/// of each field).
/// </summary>
/// <remarks>
/// <para>PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is
/// available in source control history. The use of exclusive locks was not causing any measurable
/// performance overhead even on 28-thread machines at the time this was written.</para>
/// </remarks>
private readonly object _gate = new object();
/// <summary>
/// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer
/// allocation until space is no longer available in it.
/// </summary>
/// <remarks>
/// <para>Access should be synchronized on <see cref="_gate"/>.</para>
/// </remarks>
private ReferenceCountedDisposable<MemoryMappedFile>.WeakReference _weakFileReference;
/// <summary>The name of the current memory mapped file for multiple storage units.</summary>
/// <remarks>
/// <para>Access should be synchronized on <see cref="_gate"/>.</para>
/// </remarks>
/// <seealso cref="_weakFileReference"/>
private string _name;
/// <summary>The total size of the current memory mapped file for multiple storage units.</summary>
/// <remarks>
/// <para>Access should be synchronized on <see cref="_gate"/>.</para>
/// </remarks>
/// <seealso cref="_weakFileReference"/>
private long _fileSize;
/// <summary>
/// The offset into the current memory mapped file where the next storage unit can be held.
/// </summary>
/// <remarks>
/// <para>Access should be synchronized on <see cref="_gate"/>.</para>
/// </remarks>
/// <seealso cref="_weakFileReference"/>
private long _offset;
public TemporaryStorageService(ITextFactoryService textFactory)
{
_textFactory = textFactory;
......@@ -42,9 +107,9 @@ public ITemporaryTextStorage CreateTemporaryTextStorage(CancellationToken cancel
return new TemporaryTextStorage(this);
}
public ITemporaryTextStorage AttachTemporaryTextStorage(string storageName, long size, Encoding encoding, CancellationToken cancellationToken)
public ITemporaryTextStorage AttachTemporaryTextStorage(string storageName, long offset, long size, Encoding encoding, CancellationToken cancellationToken)
{
return new TemporaryTextStorage(this, storageName, size, encoding);
return new TemporaryTextStorage(this, storageName, offset, size, encoding);
}
public ITemporaryStreamStorage CreateTemporaryStreamStorage(CancellationToken cancellationToken)
......@@ -52,9 +117,60 @@ public ITemporaryStreamStorage CreateTemporaryStreamStorage(CancellationToken ca
return new TemporaryStreamStorage(this);
}
public ITemporaryStreamStorage AttachTemporaryStreamStorage(string storageName, long size, CancellationToken cancellationToken)
public ITemporaryStreamStorage AttachTemporaryStreamStorage(string storageName, long offset, long size, CancellationToken cancellationToken)
{
return new TemporaryStreamStorage(this, storageName, offset, size);
}
/// <summary>
/// Allocate shared storage of a specified size.
/// </summary>
/// <remarks>
/// <para>"Small" requests are fulfilled from oversized memory mapped files which support several individual
/// storage units. Larger requests are allocated in their own memory mapped files.</para>
/// </remarks>
/// <param name="size">The size of the shared storage block to allocate.</param>
/// <returns>A <see cref="MemoryMappedInfo"/> describing the allocated block.</returns>
private MemoryMappedInfo CreateTemporaryStorage(long size)
{
if (size >= SingleFileThreshold)
{
// Larger blocks are allocated separately
var mapName = CreateUniqueName(size);
var storage = MemoryMappedFile.CreateNew(mapName, size);
return new MemoryMappedInfo(new ReferenceCountedDisposable<MemoryMappedFile>(storage), mapName, offset: 0, size: size);
}
lock (_gate)
{
// Obtain a reference to the memory mapped file, creating one if necessary. If a reference counted
// handle to a memory mapped file is obtained in this section, it must either be disposed before
// returning or returned to the caller who will own it through the MemoryMappedInfo.
var reference = _weakFileReference.TryAddReference();
if (reference == null || _offset + size > _fileSize)
{
var mapName = CreateUniqueName(MultiFileBlockSize);
var file = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize);
reference = new ReferenceCountedDisposable<MemoryMappedFile>(file);
_weakFileReference = new ReferenceCountedDisposable<MemoryMappedFile>.WeakReference(reference);
_name = mapName;
_fileSize = MultiFileBlockSize;
_offset = size;
return new MemoryMappedInfo(reference, _name, offset: 0, size: size);
}
else
{
// Reserve additional space in the existing storage location
_offset += size;
return new MemoryMappedInfo(reference, _name, _offset - size, size);
}
}
}
public static string CreateUniqueName(long size)
{
return new TemporaryStreamStorage(this, storageName, size);
return "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N");
}
private class TemporaryTextStorage : ITemporaryTextStorage, ITemporaryStorageWithName
......@@ -68,14 +184,15 @@ public TemporaryTextStorage(TemporaryStorageService service)
_service = service;
}
public TemporaryTextStorage(TemporaryStorageService service, string storageName, long size, Encoding encoding)
public TemporaryTextStorage(TemporaryStorageService service, string storageName, long offset, long size, Encoding encoding)
{
_service = service;
_encoding = encoding;
_memoryMappedInfo = new MemoryMappedInfo(storageName, size);
_memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size);
}
public string Name => _memoryMappedInfo?.Name;
public long Offset => _memoryMappedInfo.Offset;
public long Size => _memoryMappedInfo.Size;
public void Dispose()
......@@ -141,7 +258,7 @@ public void WriteText(SourceText text, CancellationToken cancellationToken)
// the method we use to get text out of SourceText uses Unicode (2bytes per char).
var size = Encoding.Unicode.GetMaxByteCount(text.Length);
_memoryMappedInfo = new MemoryMappedInfo(size);
_memoryMappedInfo = _service.CreateTemporaryStorage(size);
// Write the source text out as Unicode. We expect that to be cheap.
using (var stream = _memoryMappedInfo.CreateWritableStream())
......@@ -182,13 +299,14 @@ public TemporaryStreamStorage(TemporaryStorageService service)
_service = service;
}
public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long size)
public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size)
{
_service = service;
_memoryMappedInfo = new MemoryMappedInfo(storageName, size);
_memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size);
}
public string Name => _memoryMappedInfo?.Name;
public long Offset => _memoryMappedInfo.Offset;
public long Size => _memoryMappedInfo.Size;
public void Dispose()
......@@ -251,7 +369,7 @@ private async Task WriteStreamMaybeAsync(Stream stream, bool useAsync, Cancellat
using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken))
{
var size = stream.Length;
_memoryMappedInfo = new MemoryMappedInfo(size);
_memoryMappedInfo = _service.CreateTemporaryStorage(size);
using (var viewStream = _memoryMappedInfo.CreateWritableStream())
{
var buffer = SharedPools.ByteArray.Allocate();
......
......@@ -95,9 +95,9 @@
<Compile Include="Workspace\Host\Mef\DesktopMefHostServices.cs" />
<Compile Include="Workspace\Host\Mef\MefV1HostServices.cs" />
<Compile Include="Workspace\Host\SimpleAnalyzerAssemblyLoaderService.cs" />
<Compile Include="Workspace\Host\TemporaryStorage\TemporaryStorageServiceFactory.CopiedMemoryMappedViewHandle.cs" />
<Compile Include="Workspace\Host\TemporaryStorage\TemporaryStorageServiceFactory.cs" />
<Compile Include="Workspace\Host\TemporaryStorage\TemporaryStorageServiceFactory.MemoryMappedInfo.cs" />
<Compile Include="Workspace\Host\TemporaryStorage\TemporaryStorageServiceFactory.ReferenceCountedDisposable.cs" />
<Compile Include="Workspace\Host\TextFactory\DesktopTextFactoryService.cs" />
<Compile Include="Workspace\MSBuild\CSharp\CSharpProjectFileLoader.cs" />
<Compile Include="Workspace\MSBuild\CSharp\CSharpProjectFileLoader.CSharpProjectFile.cs" />
......
......@@ -418,7 +418,7 @@ private void WriteTo(Metadata metadata, ObjectWriter writer, CancellationToken c
return false;
}
using (var pooled = Creator.CreateList<(string name, long size)>())
using (var pooled = Creator.CreateList<(string name, long offset, long size)>())
{
foreach (var storage in storages)
{
......@@ -428,7 +428,7 @@ private void WriteTo(Metadata metadata, ObjectWriter writer, CancellationToken c
return false;
}
pooled.Object.Add((storage2.Name, storage2.Size));
pooled.Object.Add((storage2.Name, storage2.Offset, storage2.Size));
}
WritePortableExecutableReferenceHeaderTo((PortableExecutableReference)reference, SerializationKinds.MemoryMapFile, writer, cancellationToken);
......@@ -440,6 +440,7 @@ private void WriteTo(Metadata metadata, ObjectWriter writer, CancellationToken c
{
writer.WriteInt32((int)MetadataImageKind.Module);
writer.WriteString(tuple.name);
writer.WriteInt64(tuple.offset);
writer.WriteInt64(tuple.size);
}
......@@ -528,9 +529,10 @@ private void WriteTo(Metadata metadata, ObjectWriter writer, CancellationToken c
Contract.ThrowIfNull(service2);
var name = reader.ReadString();
var offset = reader.ReadInt64();
var size = reader.ReadInt64();
storage = service2.AttachTemporaryStreamStorage(name, size, cancellationToken);
storage = service2.AttachTemporaryStreamStorage(name, offset, size, cancellationToken);
length = size;
return;
......
......@@ -30,6 +30,7 @@ public void SerializeSourceText(ITemporaryStorageWithName storage, SourceText te
{
writer.WriteInt32((int)SerializationKinds.MemoryMapFile);
writer.WriteString(storage.Name);
writer.WriteInt64(storage.Offset);
writer.WriteInt64(storage.Size);
return;
}
......@@ -50,9 +51,10 @@ private SourceText DeserializeSourceText(ObjectReader reader, CancellationToken
if (kind == SerializationKinds.MemoryMapFile)
{
var name = reader.ReadString();
var offset = reader.ReadInt64();
var size = reader.ReadInt64();
var storage = _tempService.AttachTemporaryTextStorage(name, size, encoding, cancellationToken);
var storage = _tempService.AttachTemporaryTextStorage(name, offset, size, encoding, cancellationToken);
return storage.ReadText(cancellationToken);
}
......
......@@ -13,11 +13,11 @@ internal interface ITemporaryStorageService2 : ITemporaryStorageService
/// <summary>
/// Attach to existing <see cref="ITemporaryStreamStorage"/> with given name.
/// </summary>
ITemporaryStreamStorage AttachTemporaryStreamStorage(string storageName, long size, CancellationToken cancellationToken = default(CancellationToken));
ITemporaryStreamStorage AttachTemporaryStreamStorage(string storageName, long offset, long size, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Attach to existing <see cref="ITemporaryTextStorage"/> with given name.
/// </summary>
ITemporaryTextStorage AttachTemporaryTextStorage(string storageName, long size, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken));
ITemporaryTextStorage AttachTemporaryTextStorage(string storageName, long offset, long size, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken));
}
}
......@@ -12,6 +12,11 @@ internal interface ITemporaryStorageWithName
/// </summary>
string Name { get; }
/// <summary>
/// Get offset of the temporary storage
/// </summary>
long Offset { get; }
/// <summary>
/// Get size of the temporary storage
/// </summary>
......
......@@ -98,6 +98,7 @@
<Compile Include="UtilityTest\BKTreeTests.cs" />
<Compile Include="UtilityTest\FilePathUtilitiesTests.cs" />
<Compile Include="UtilityTest\SpellCheckerTests.cs" />
<Compile Include="WorkspaceServiceTests\ReferenceCountedDisposableTests.cs" />
<Compile Include="WorkspaceTests\CommandLineProjectTests.cs" />
<Compile Include="WorkspaceTests\AdhocWorkspaceTests.cs" />
<Compile Include="Differencing\MatchTests.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
using static Microsoft.CodeAnalysis.Host.TemporaryStorageServiceFactory;
namespace Microsoft.CodeAnalysis.UnitTests
{
public class ReferenceCountedDisposableTests
{
[Fact]
[Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestArgumentValidation()
{
Assert.Throws<ArgumentNullException>("instance", () => new ReferenceCountedDisposable<IDisposable>(null));
}
[Theory]
[InlineData(1)]
[InlineData(3)]
[Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestSingleReferenceDispose(int disposeCount)
{
var target = new DisposableObject();
var reference = new ReferenceCountedDisposable<DisposableObject>(target);
Assert.Same(target, reference.Target);
Assert.False(target.IsDisposed);
Assert.Equal(0, target.DisposeCount);
for (var i = 0; i < disposeCount; i++)
{
reference.Dispose();
}
Assert.Throws<ObjectDisposedException>(() => reference.Target);
Assert.True(target.IsDisposed);
Assert.Equal(1, target.DisposeCount);
}
[Fact]
[Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestTryAddReferenceFailsAfterDispose()
{
var target = new DisposableObject();
var reference = new ReferenceCountedDisposable<DisposableObject>(target);
reference.Dispose();
Assert.Null(reference.TryAddReference());
}
[Fact]
[Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestTryAddReferenceFailsAfterDispose2()
{
var target = new DisposableObject();
var reference = new ReferenceCountedDisposable<DisposableObject>(target);
// TryAddReference succeeds before dispose
var reference2 = reference.TryAddReference();
Assert.NotNull(reference2);
reference.Dispose();
// TryAddReference fails after dispose, even if another instance is alive
Assert.Null(reference.TryAddReference());
Assert.NotNull(reference2.Target);
Assert.False(target.IsDisposed);
}
[Fact]
[Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestOutOfOrderDispose()
{
var target = new DisposableObject();
var reference = new ReferenceCountedDisposable<DisposableObject>(target);
var reference2 = reference.TryAddReference();
var reference3 = reference2.TryAddReference();
reference2.Dispose();
Assert.False(target.IsDisposed);
reference3.Dispose();
Assert.False(target.IsDisposed);
reference.Dispose();
Assert.True(target.IsDisposed);
Assert.Equal(1, target.DisposeCount);
}
[Fact]
[Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestWeakReferenceLifetime()
{
var target = new DisposableObject();
var reference = new ReferenceCountedDisposable<DisposableObject>(target);
var weakReference = new ReferenceCountedDisposable<DisposableObject>.WeakReference(reference);
var reference2 = reference.TryAddReference();
Assert.NotNull(reference2);
reference.Dispose();
// TryAddReference fails after dispose for a counted reference
Assert.Null(reference.TryAddReference());
Assert.NotNull(reference2.Target);
Assert.False(target.IsDisposed);
// However, a WeakReference created from the disposed reference can still add a reference
var reference3 = weakReference.TryAddReference();
Assert.NotNull(reference3);
reference2.Dispose();
Assert.False(target.IsDisposed);
reference3.Dispose();
Assert.True(target.IsDisposed);
}
[Fact]
[Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestWeakReferenceArgumentValidation()
{
Assert.Throws<ArgumentNullException>("reference", () => new ReferenceCountedDisposable<IDisposable>.WeakReference(null));
}
[Fact]
[Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestDefaultWeakReference()
{
Assert.Null(default(ReferenceCountedDisposable<IDisposable>.WeakReference).TryAddReference());
}
private sealed class DisposableObject : IDisposable
{
public bool IsDisposed
{
get;
private set;
}
public int DisposeCount
{
get;
private set;
}
public void Dispose()
{
IsDisposed = true;
DisposeCount++;
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册