Merge pull request #10150 from umbraco/v8/bugfix/removing-unnecessary-readerwriterlock
Ensure that any ReaderWriterLockSlim is disposed, or replaced with a more suitable lock
This commit is contained in:
@@ -6,8 +6,10 @@ namespace Umbraco.Core.Cache
|
||||
/// <summary>
|
||||
/// Represents the application caches.
|
||||
/// </summary>
|
||||
public class AppCaches
|
||||
public class AppCaches : IDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AppCaches"/> for use in a web application.
|
||||
/// </summary>
|
||||
@@ -82,5 +84,26 @@ namespace Umbraco.Core.Cache
|
||||
/// search through all keys on a global scale.</para>
|
||||
/// </remarks>
|
||||
public IsolatedCaches IsolatedCaches { get; }
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
RuntimeCache.DisposeIfDisposable();
|
||||
RequestCache.DisposeIfDisposable();
|
||||
IsolatedCaches.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Umbraco.Core.Cache
|
||||
/// Provides a base class for implementing a dictionary of <see cref="IAppPolicyCache"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the dictionary key.</typeparam>
|
||||
public abstract class AppPolicedCacheDictionary<TKey>
|
||||
public abstract class AppPolicedCacheDictionary<TKey> : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<TKey, IAppPolicyCache> _caches = new ConcurrentDictionary<TKey, IAppPolicyCache>();
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Umbraco.Core.Cache
|
||||
/// Gets the internal cache factory, for tests only!
|
||||
/// </summary>
|
||||
internal readonly Func<TKey, IAppPolicyCache> CacheFactory;
|
||||
private bool _disposedValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a cache.
|
||||
@@ -70,5 +71,27 @@ namespace Umbraco.Core.Cache
|
||||
foreach (var cache in _caches.Values)
|
||||
cache.Clear();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach(var value in _caches.Values)
|
||||
{
|
||||
value.DisposeIfDisposable();
|
||||
}
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@ namespace Umbraco.Core.Cache
|
||||
/// instance, and ensuring that all inserts and returns are deep cloned copies of the cache item,
|
||||
/// when the item is deep-cloneable.
|
||||
/// </summary>
|
||||
internal class DeepCloneAppCache : IAppPolicyCache
|
||||
internal class DeepCloneAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeepCloneAppCache"/> class.
|
||||
/// </summary>
|
||||
@@ -153,5 +155,24 @@ namespace Umbraco.Core.Cache
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
InnerCache.DisposeIfDisposable();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@ namespace Umbraco.Core.Cache
|
||||
/// <summary>
|
||||
/// Implements <see cref="IAppPolicyCache"/> on top of a <see cref="ObjectCache"/>.
|
||||
/// </summary>
|
||||
public class ObjectCacheAppCache : IAppPolicyCache
|
||||
public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
private bool _disposedValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectCacheAppCache"/>.
|
||||
@@ -109,19 +110,34 @@ namespace Umbraco.Core.Cache
|
||||
|
||||
Lazy<object> result;
|
||||
|
||||
using (var lck = new UpgradeableReadLock(_locker))
|
||||
try
|
||||
{
|
||||
_locker.EnterUpgradeableReadLock();
|
||||
|
||||
result = MemoryCache.Get(key) as Lazy<object>;
|
||||
if (result == null || FastDictionaryAppCacheBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
||||
{
|
||||
result = FastDictionaryAppCacheBase.GetSafeLazy(factory);
|
||||
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
|
||||
|
||||
lck.UpgradeToWriteLock();
|
||||
//NOTE: This does an add or update
|
||||
MemoryCache.Set(key, result, policy);
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
//NOTE: This does an add or update
|
||||
MemoryCache.Set(key, result, policy);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsWriteLockHeld)
|
||||
_locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsUpgradeableReadLockHeld)
|
||||
_locker.ExitUpgradeableReadLock();
|
||||
}
|
||||
|
||||
//return result.Value;
|
||||
|
||||
@@ -360,5 +376,23 @@ namespace Umbraco.Core.Cache
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_locker.Dispose();
|
||||
}
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Cache
|
||||
/// Implements <see cref="IAppPolicyCache"/> on top of a <see cref="System.Web.Caching.Cache"/>.
|
||||
/// </summary>
|
||||
/// <remarks>The underlying cache is expected to be HttpRuntime.Cache.</remarks>
|
||||
internal class WebCachingAppCache : FastDictionaryAppCacheBase, IAppPolicyCache
|
||||
internal class WebCachingAppCache : FastDictionaryAppCacheBase, IAppPolicyCache, IDisposable
|
||||
{
|
||||
// locker object that supports upgradeable read locking
|
||||
// does not need to support recursion if we implement the cache correctly and ensure
|
||||
@@ -19,6 +19,7 @@ namespace Umbraco.Core.Cache
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
private readonly System.Web.Caching.Cache _cache;
|
||||
private bool _disposedValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WebCachingAppCache"/> class.
|
||||
@@ -138,8 +139,10 @@ namespace Umbraco.Core.Cache
|
||||
var value = result == null ? null : GetSafeLazyValue(result);
|
||||
if (value != null) return value;
|
||||
|
||||
using (var lck = new UpgradeableReadLock(_locker))
|
||||
try
|
||||
{
|
||||
_locker.EnterUpgradeableReadLock();
|
||||
|
||||
result = _cache.Get(key) as Lazy<object>; // null if key not found
|
||||
|
||||
// cannot create value within the lock, so if result.IsValueCreated is false, just
|
||||
@@ -152,15 +155,28 @@ namespace Umbraco.Core.Cache
|
||||
var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
|
||||
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
|
||||
|
||||
lck.UpgradeToWriteLock();
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
|
||||
// create a cache dependency if one is needed.
|
||||
var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null;
|
||||
// create a cache dependency if one is needed.
|
||||
var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null;
|
||||
|
||||
//NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update!
|
||||
_cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback);
|
||||
//NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update!
|
||||
_cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsWriteLockHeld)
|
||||
_locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsUpgradeableReadLockHeld)
|
||||
_locker.ExitUpgradeableReadLock();
|
||||
}
|
||||
|
||||
// using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
|
||||
// exceptions (but try again and again) and silently eat them - however at
|
||||
@@ -204,5 +220,24 @@ namespace Umbraco.Core.Cache
|
||||
_locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_locker.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core.Collections
|
||||
{
|
||||
@@ -14,9 +14,10 @@ namespace Umbraco.Core.Collections
|
||||
[Serializable]
|
||||
public class ConcurrentHashSet<T> : ICollection<T>
|
||||
{
|
||||
private readonly HashSet<T> _innerSet = new HashSet<T>();
|
||||
private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
// Internally we just use a ConcurrentDictionary with the same value for the Value (since we don't actually care about the value)
|
||||
private static readonly byte _emptyValue = 0x0;
|
||||
private readonly ConcurrentDictionary<T, byte> _innerSet = new ConcurrentDictionary<T, byte>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
@@ -26,7 +27,7 @@ namespace Umbraco.Core.Collections
|
||||
/// <filterpriority>1</filterpriority>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return GetThreadSafeClone().GetEnumerator();
|
||||
return _innerSet.Keys.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -50,16 +51,7 @@ namespace Umbraco.Core.Collections
|
||||
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
try
|
||||
{
|
||||
_instanceLocker.EnterWriteLock();
|
||||
return _innerSet.Remove(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_instanceLocker.IsWriteLockHeld)
|
||||
_instanceLocker.ExitWriteLock();
|
||||
}
|
||||
return _innerSet.TryRemove(item, out _);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,17 +66,7 @@ namespace Umbraco.Core.Collections
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
_instanceLocker.EnterReadLock();
|
||||
return _innerSet.Count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_instanceLocker.IsReadLockHeld)
|
||||
_instanceLocker.ExitReadLock();
|
||||
}
|
||||
|
||||
return _innerSet.Count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,19 +81,10 @@ namespace Umbraco.Core.Collections
|
||||
/// <summary>
|
||||
/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
|
||||
/// </summary>
|
||||
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
|
||||
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
|
||||
public void Add(T item)
|
||||
{
|
||||
try
|
||||
{
|
||||
_instanceLocker.EnterWriteLock();
|
||||
_innerSet.Add(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_instanceLocker.IsWriteLockHeld)
|
||||
_instanceLocker.ExitWriteLock();
|
||||
}
|
||||
_innerSet.TryAdd(item, _emptyValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -121,21 +94,7 @@ namespace Umbraco.Core.Collections
|
||||
/// <returns></returns>
|
||||
public bool TryAdd(T item)
|
||||
{
|
||||
if (Contains(item)) return false;
|
||||
try
|
||||
{
|
||||
_instanceLocker.EnterWriteLock();
|
||||
|
||||
//double check
|
||||
if (_innerSet.Contains(item)) return false;
|
||||
_innerSet.Add(item);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_instanceLocker.IsWriteLockHeld)
|
||||
_instanceLocker.ExitWriteLock();
|
||||
}
|
||||
return _innerSet.TryAdd(item, _emptyValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,16 +103,7 @@ namespace Umbraco.Core.Collections
|
||||
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. </exception>
|
||||
public void Clear()
|
||||
{
|
||||
try
|
||||
{
|
||||
_instanceLocker.EnterWriteLock();
|
||||
_innerSet.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_instanceLocker.IsWriteLockHeld)
|
||||
_instanceLocker.ExitWriteLock();
|
||||
}
|
||||
_innerSet.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -165,16 +115,7 @@ namespace Umbraco.Core.Collections
|
||||
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
try
|
||||
{
|
||||
_instanceLocker.EnterReadLock();
|
||||
return _innerSet.Contains(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_instanceLocker.IsReadLockHeld)
|
||||
_instanceLocker.ExitReadLock();
|
||||
}
|
||||
return _innerSet.ContainsKey(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -183,24 +124,8 @@ namespace Umbraco.Core.Collections
|
||||
/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from the <see cref="T:System.Collections.Concurrent.IProducerConsumerCollection`1"/>. The array must have zero-based indexing.</param><param name="index">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in Visual Basic).</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than zero.</exception><exception cref="T:System.ArgumentException"><paramref name="index"/> is equal to or greater than the length of the <paramref name="array"/> -or- The number of elements in the source <see cref="T:System.Collections.Concurrent.ConcurrentQueue`1"/> is greater than the available space from <paramref name="index"/> to the end of the destination <paramref name="array"/>.</exception>
|
||||
public void CopyTo(T[] array, int index)
|
||||
{
|
||||
var clone = GetThreadSafeClone();
|
||||
clone.CopyTo(array, index);
|
||||
}
|
||||
|
||||
private HashSet<T> GetThreadSafeClone()
|
||||
{
|
||||
HashSet<T> clone = null;
|
||||
try
|
||||
{
|
||||
_instanceLocker.EnterReadLock();
|
||||
clone = new HashSet<T>(_innerSet, _innerSet.Comparer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_instanceLocker.IsReadLockHeld)
|
||||
_instanceLocker.ExitReadLock();
|
||||
}
|
||||
return clone;
|
||||
var keys = _innerSet.Keys;
|
||||
keys.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -209,8 +134,8 @@ namespace Umbraco.Core.Collections
|
||||
/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection"/>. The <see cref="T:System.Array"/> must have zero-based indexing. </param><param name="index">The zero-based index in <paramref name="array"/> at which copying begins. </param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null. </exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than zero. </exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or- The number of elements in the source <see cref="T:System.Collections.ICollection"/> is greater than the available space from <paramref name="index"/> to the end of the destination <paramref name="array"/>. </exception><exception cref="T:System.ArgumentException">The type of the source <see cref="T:System.Collections.ICollection"/> cannot be cast automatically to the type of the destination <paramref name="array"/>. </exception><filterpriority>2</filterpriority>
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
var clone = GetThreadSafeClone();
|
||||
Array.Copy(clone.ToArray(), 0, array, index, clone.Count);
|
||||
var keys = _innerSet.Keys;
|
||||
Array.Copy(keys.ToArray(), 0, array, index, keys.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
@@ -17,8 +16,6 @@ namespace Umbraco.Core.Models
|
||||
// TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details
|
||||
public class PropertyGroupCollection : KeyedCollection<string, PropertyGroup>, INotifyCollectionChanged, IDeepCloneable
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim();
|
||||
|
||||
internal PropertyGroupCollection()
|
||||
{ }
|
||||
|
||||
@@ -70,47 +67,37 @@ namespace Umbraco.Core.Models
|
||||
|
||||
internal new void Add(PropertyGroup item)
|
||||
{
|
||||
try
|
||||
//Note this is done to ensure existing groups can be renamed
|
||||
if (item.HasIdentity && item.Id > 0)
|
||||
{
|
||||
_addLocker.EnterWriteLock();
|
||||
|
||||
//Note this is done to ensure existing groups can be renamed
|
||||
if (item.HasIdentity && item.Id > 0)
|
||||
var exists = Contains(item.Id);
|
||||
if (exists)
|
||||
{
|
||||
var exists = Contains(item.Id);
|
||||
var keyExists = Contains(item.Name);
|
||||
if (keyExists)
|
||||
throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates");
|
||||
|
||||
//collection events will be raised in SetItem
|
||||
SetItem(IndexOfKey(item.Id), item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = GetKeyForItem(item);
|
||||
if (key != null)
|
||||
{
|
||||
var exists = Contains(key);
|
||||
if (exists)
|
||||
{
|
||||
var keyExists = Contains(item.Name);
|
||||
if (keyExists)
|
||||
throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates");
|
||||
|
||||
//collection events will be raised in SetItem
|
||||
SetItem(IndexOfKey(item.Id), item);
|
||||
SetItem(IndexOfKey(key), item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = GetKeyForItem(item);
|
||||
if (key != null)
|
||||
{
|
||||
var exists = Contains(key);
|
||||
if (exists)
|
||||
{
|
||||
//collection events will be raised in SetItem
|
||||
SetItem(IndexOfKey(key), item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
//collection events will be raised in InsertItem
|
||||
base.Add(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_addLocker.IsWriteLockHeld)
|
||||
_addLocker.ExitWriteLock();
|
||||
}
|
||||
//collection events will be raised in InsertItem
|
||||
base.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
@@ -16,10 +15,6 @@ namespace Umbraco.Core.Models
|
||||
// TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details
|
||||
public class PropertyTypeCollection : KeyedCollection<string, PropertyType>, INotifyCollectionChanged, IDeepCloneable
|
||||
{
|
||||
[IgnoreDataMember]
|
||||
private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim();
|
||||
|
||||
|
||||
internal PropertyTypeCollection(bool supportsPublishing)
|
||||
{
|
||||
SupportsPublishing = supportsPublishing;
|
||||
@@ -87,36 +82,28 @@ namespace Umbraco.Core.Models
|
||||
item.SupportsPublishing = SupportsPublishing;
|
||||
|
||||
// TODO: this is not pretty and should be refactored
|
||||
try
|
||||
{
|
||||
_addLocker.EnterWriteLock();
|
||||
var key = GetKeyForItem(item);
|
||||
if (key != null)
|
||||
{
|
||||
var exists = Contains(key);
|
||||
if (exists)
|
||||
{
|
||||
//collection events will be raised in SetItem
|
||||
SetItem(IndexOfKey(key), item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//check if the item's sort order is already in use
|
||||
if (this.Any(x => x.SortOrder == item.SortOrder))
|
||||
{
|
||||
//make it the next iteration
|
||||
item.SortOrder = this.Max(x => x.SortOrder) + 1;
|
||||
}
|
||||
|
||||
//collection events will be raised in InsertItem
|
||||
base.Add(item);
|
||||
}
|
||||
finally
|
||||
var key = GetKeyForItem(item);
|
||||
if (key != null)
|
||||
{
|
||||
if (_addLocker.IsWriteLockHeld)
|
||||
_addLocker.ExitWriteLock();
|
||||
var exists = Contains(key);
|
||||
if (exists)
|
||||
{
|
||||
//collection events will be raised in SetItem
|
||||
SetItem(IndexOfKey(key), item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//check if the item's sort order is already in use
|
||||
if (this.Any(x => x.SortOrder == item.SortOrder))
|
||||
{
|
||||
//make it the next iteration
|
||||
item.SortOrder = this.Max(x => x.SortOrder) + 1;
|
||||
}
|
||||
|
||||
//collection events will be raised in InsertItem
|
||||
base.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a convenience methodology for implementing locked access to resources.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Intended as an infrastructure class.</para>
|
||||
/// <para>This is a very inefficient way to lock as it allocates one object each time we lock,
|
||||
/// so it's OK to use this class for things that happen once, where it is convenient, but not
|
||||
/// for performance-critical code!</para>
|
||||
/// </remarks>
|
||||
[Obsolete("Use ReaderWriterLockSlim directly. This will be removed in future versions.")]
|
||||
public class ReadLock : IDisposable
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _rwLock;
|
||||
|
||||
@@ -7,7 +7,7 @@ using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
public class IdkMap
|
||||
public class IdkMap : IDisposable
|
||||
{
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
|
||||
@@ -46,6 +46,7 @@ namespace Umbraco.Core.Services
|
||||
|
||||
private readonly ConcurrentDictionary<UmbracoObjectTypes, (Func<int, Guid> id2key, Func<Guid, int> key2id)> _dictionary
|
||||
= new ConcurrentDictionary<UmbracoObjectTypes, (Func<int, Guid> id2key, Func<Guid, int> key2id)>();
|
||||
private bool _disposedValue;
|
||||
|
||||
internal void SetMapper(UmbracoObjectTypes umbracoObjectType, Func<int, Guid> id2key, Func<Guid, int> key2id)
|
||||
{
|
||||
@@ -106,8 +107,8 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsReadLockHeld)
|
||||
_locker.ExitReadLock();
|
||||
if (_locker.IsWriteLockHeld)
|
||||
_locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,8 +126,8 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsReadLockHeld)
|
||||
_locker.ExitReadLock();
|
||||
if (_locker.IsWriteLockHeld)
|
||||
_locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -369,5 +370,23 @@ namespace Umbraco.Core.Services
|
||||
|
||||
public T Id { get; }
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_locker.Dispose();
|
||||
}
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a convenience methodology for implementing locked access to resources.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Intended as an infrastructure class.
|
||||
/// </remarks>
|
||||
[Obsolete("Use ReaderWriterLockSlim directly. This will be removed in future versions.")]
|
||||
public class UpgradeableReadLock : IDisposable
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _rwLock;
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a convenience methodology for implementing locked access to resources.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Intended as an infrastructure class.</para>
|
||||
/// <para>This is a very inefficient way to lock as it allocates one object each time we lock,
|
||||
/// so it's OK to use this class for things that happen once, where it is convenient, but not
|
||||
/// for performance-critical code!</para>
|
||||
/// </remarks>
|
||||
[Obsolete("Use ReaderWriterLockSlim directly. This will be removed in future versions.")]
|
||||
public class WriteLock : IDisposable
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _rwLock;
|
||||
|
||||
@@ -21,7 +21,7 @@ using File = System.IO.File;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded
|
||||
{
|
||||
internal class PureLiveModelFactory : ILivePublishedModelFactory2, IRegisteredObject
|
||||
internal class PureLiveModelFactory : ILivePublishedModelFactory2, IRegisteredObject, IDisposable
|
||||
{
|
||||
private Assembly _modelsAssembly;
|
||||
private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary<string, Type>() };
|
||||
@@ -33,6 +33,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
private int _ver, _skipver;
|
||||
private readonly int _debugLevel;
|
||||
private BuildManager _theBuildManager;
|
||||
private bool _disposedValue;
|
||||
private readonly Lazy<UmbracoServices> _umbracoServices; // fixme: this is because of circular refs :(
|
||||
private UmbracoServices UmbracoServices => _umbracoServices.Value;
|
||||
|
||||
@@ -677,11 +678,31 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
|
||||
public void Stop(bool immediate)
|
||||
{
|
||||
_watcher.EnableRaisingEvents = false;
|
||||
_watcher.Dispose();
|
||||
Dispose();
|
||||
HostingEnvironment.UnregisterObject(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_watcher.EnableRaisingEvents = false;
|
||||
_watcher.Dispose();
|
||||
_locker.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Web.Logging
|
||||
/// </remarks>
|
||||
internal class WebProfilerProvider : AspNetRequestProvider
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
|
||||
private readonly object _locker = new object();
|
||||
private MiniProfiler _startupProfiler;
|
||||
private int _first;
|
||||
private volatile BootPhase _bootPhase;
|
||||
@@ -39,8 +39,7 @@ namespace Umbraco.Web.Logging
|
||||
|
||||
public void BeginBootRequest()
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
try
|
||||
lock (_locker)
|
||||
{
|
||||
if (_bootPhase != BootPhase.Boot)
|
||||
throw new InvalidOperationException("Invalid boot phase.");
|
||||
@@ -48,28 +47,19 @@ namespace Umbraco.Web.Logging
|
||||
|
||||
// assign the profiler to be the current MiniProfiler for the request
|
||||
// is's already active, starting and all
|
||||
HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.ExitWriteLock();
|
||||
HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler;
|
||||
}
|
||||
}
|
||||
|
||||
public void EndBootRequest()
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
try
|
||||
lock (_locker)
|
||||
{
|
||||
if (_bootPhase != BootPhase.BootRequest)
|
||||
throw new InvalidOperationException("Invalid boot phase.");
|
||||
throw new InvalidOperationException("Invalid boot phase.");
|
||||
_bootPhase = BootPhase.Booted;
|
||||
|
||||
_startupProfiler = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.ExitWriteLock();
|
||||
_startupProfiler = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ using Umbraco.Web.PublishedCache.NuCache.Navigable;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
internal class MemberCache : IPublishedMemberCache, INavigableData
|
||||
internal class MemberCache : IPublishedMemberCache, INavigableData, IDisposable
|
||||
{
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly PublishedContentTypeCache _contentTypeCache;
|
||||
private readonly bool _previewDefault;
|
||||
private bool _disposedValue;
|
||||
|
||||
public MemberCache(bool previewDefault, IAppCache snapshotCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IEntityXmlSerializer entitySerializer)
|
||||
@@ -158,6 +159,25 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
return _contentTypeCache.Get(PublishedItemType.Member, alias);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_contentTypeCache.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
ContentCache.Dispose();
|
||||
MediaCache.Dispose();
|
||||
MemberCache.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Web.PublishedCache
|
||||
/// Represents a content type cache.
|
||||
/// </summary>
|
||||
/// <remarks>This cache is not snapshotted, so it refreshes any time things change.</remarks>
|
||||
public class PublishedContentTypeCache
|
||||
public class PublishedContentTypeCache : IDisposable
|
||||
{
|
||||
// NOTE: These are not concurrent dictionaries because all access is done within a lock
|
||||
private readonly Dictionary<string, IPublishedContentType> _typesByAlias = new Dictionary<string, IPublishedContentType>();
|
||||
@@ -320,6 +320,8 @@ namespace Umbraco.Web.PublishedCache
|
||||
// for unit tests - changing the callback must reset the cache obviously
|
||||
// TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id
|
||||
private Func<int, IPublishedContentType> _getPublishedContentTypeById;
|
||||
private bool _disposedValue;
|
||||
|
||||
internal Func<int, IPublishedContentType> GetPublishedContentTypeById
|
||||
{
|
||||
get => _getPublishedContentTypeById;
|
||||
@@ -367,5 +369,24 @@ namespace Umbraco.Web.PublishedCache
|
||||
{
|
||||
return GetAliasKey(contentType.ItemType, contentType.Alias);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_lock.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Web.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities to handle site domains.
|
||||
/// </summary>
|
||||
public class SiteDomainHelper : ISiteDomainHelper
|
||||
public class SiteDomainHelper : ISiteDomainHelper, IDisposable
|
||||
{
|
||||
#region Configure
|
||||
|
||||
@@ -18,6 +19,7 @@ namespace Umbraco.Web.Routing
|
||||
private static Dictionary<string, string[]> _sites;
|
||||
private static Dictionary<string, List<string>> _bindings;
|
||||
private static Dictionary<string, Dictionary<string, string[]>> _qualifiedSites;
|
||||
private bool _disposedValue;
|
||||
|
||||
// these are for unit tests *only*
|
||||
// ReSharper disable ConvertToAutoPropertyWithPrivateSetter
|
||||
@@ -30,16 +32,10 @@ namespace Umbraco.Web.Routing
|
||||
private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$";
|
||||
private static readonly Regex DomainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a disposable object that represents safe write access to config.
|
||||
/// </summary>
|
||||
/// <remarks>Should be used in a <c>using(SiteDomainHelper.ConfigWriteLock) { ... }</c> mode.</remarks>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
protected static IDisposable ConfigWriteLock => new WriteLock(ConfigLock);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a disposable object that represents safe read access to config.
|
||||
/// </summary>
|
||||
/// <remarks>Should be used in a <c>using(SiteDomainHelper.ConfigWriteLock) { ... }</c> mode.</remarks>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
protected static IDisposable ConfigReadLock => new ReadLock(ConfigLock);
|
||||
|
||||
/// <summary>
|
||||
@@ -313,6 +309,28 @@ namespace Umbraco.Web.Routing
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// This is pretty nasty disposing a static on an instance but it's because this whole class
|
||||
// is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics.
|
||||
ConfigLock.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user