|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//------------------------------------------------------------------------------
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace System.Data.ProviderBase
{
internal abstract class DbReferenceCollection
{
private struct CollectionEntry
{
private int _tag; // information about the reference
private WeakReference _weak; // the reference itself.
public void NewTarget(int tag, object target)
{
Debug.Assert(!HasTarget, "Entry already has a valid target");
Debug.Assert(tag != 0, "Bad tag");
Debug.Assert(target != null, "Invalid target");
if (_weak == null)
{
_weak = new WeakReference(target, false);
}
else
{
_weak.Target = target;
}
_tag = tag;
}
public void RemoveTarget()
{
_tag = 0;
}
public bool HasTarget
{
get
{
return ((_tag != 0) && (_weak.IsAlive));
}
}
public int Tag
{
get
{
return _tag;
}
}
public object? Target
{
get
{
return (_tag == 0 ? null : _weak.Target);
}
}
}
private const int LockPollTime = 100; // Time to wait (in ms) between attempting to get the _itemLock
private const int DefaultCollectionSize = 20; // Default size for the collection, and the amount to grow every time the collection is full
private CollectionEntry[] _items; // The collection of items we are keeping track of
private readonly object _itemLock; // Used to synchronize access to the _items collection
private int _optimisticCount; // (#ItemsAdded - #ItemsRemoved) - This estimates the number of items that we *should* have (but doesn't take into account item targets being GC'd)
private int _lastItemIndex; // Location of the last item in _items
private volatile bool _isNotifying; // Indicates that the collection is currently being notified (and, therefore, about to be cleared)
protected DbReferenceCollection()
{
_items = new CollectionEntry[DefaultCollectionSize];
_itemLock = new object();
_optimisticCount = 0;
_lastItemIndex = 0;
}
public abstract void Add(object value, int tag);
protected void AddItem(object value, int tag)
{
Debug.Assert(null != value && 0 != tag, "AddItem with null value or 0 tag");
bool itemAdded = false;
lock (_itemLock)
{
// Try to find a free spot
for (int i = 0; i <= _lastItemIndex; ++i)
{
if (_items[i].Tag == 0)
{
_items[i].NewTarget(tag, value);
Debug.Assert(_items[i].HasTarget, "missing expected target");
itemAdded = true;
break;
}
}
// No free spots, can we just add on to the end?
if ((!itemAdded) && (_lastItemIndex + 1 < _items.Length))
{
_lastItemIndex++;
_items[_lastItemIndex].NewTarget(tag, value);
itemAdded = true;
}
// If no free spots and no space at the end, try to find a dead item
if (!itemAdded)
{
for (int i = 0; i <= _lastItemIndex; ++i)
{
if (!_items[i].HasTarget)
{
_items[i].NewTarget(tag, value);
Debug.Assert(_items[i].HasTarget, "missing expected target");
itemAdded = true;
break;
}
}
}
// If nothing was free, then resize and add to the end
if (!itemAdded)
{
Array.Resize<CollectionEntry>(ref _items, _items.Length * 2);
_lastItemIndex++;
_items[_lastItemIndex].NewTarget(tag, value);
}
_optimisticCount++;
}
}
internal T? FindItem<T>(int tag, Func<T, bool> filterMethod) where T : class
{
bool lockObtained = false;
try
{
TryEnterItemLock(ref lockObtained);
if (lockObtained)
{
if (_optimisticCount > 0)
{
// Loop through the items
for (int counter = 0; counter <= _lastItemIndex; counter++)
{
// Check tag (should be easiest and quickest)
if (_items[counter].Tag == tag)
{
// NOTE: Check if the returned value is null twice may seem wasteful, but this if for performance
// Since checking for null twice is cheaper than calling both HasTarget and Target OR always attempting to typecast
object? value = _items[counter].Target;
if (value != null)
{
// Make sure the item has the correct type and passes the filtering
if (value is T tempItem && filterMethod(tempItem))
{
return tempItem;
}
}
}
}
}
}
}
finally
{
ExitItemLockIfNeeded(lockObtained);
}
// If we got to here, then no item was found, so return null
return null;
}
public void Notify(int message)
{
bool lockObtained = false;
try
{
TryEnterItemLock(ref lockObtained);
if (lockObtained)
{
try
{
_isNotifying = true;
// Loop through each live item and notify it
if (_optimisticCount > 0)
{
for (int index = 0; index <= _lastItemIndex; ++index)
{
object? value = _items[index].Target; // checks tag & gets target
if (null != value)
{
NotifyItem(message, _items[index].Tag, value);
_items[index].RemoveTarget();
}
Debug.Assert(!_items[index].HasTarget, "Unexpected target after notifying");
}
_optimisticCount = 0;
}
// Shrink collection (if needed)
if (_items.Length > 100)
{
_lastItemIndex = 0;
_items = new CollectionEntry[DefaultCollectionSize];
}
}
finally
{
_isNotifying = false;
}
}
}
finally
{
ExitItemLockIfNeeded(lockObtained);
}
}
protected abstract void NotifyItem(int message, int tag, object value);
public abstract void Remove(object value);
protected void RemoveItem(object value)
{
Debug.Assert(null != value, "RemoveItem with null");
bool lockObtained = false;
try
{
TryEnterItemLock(ref lockObtained);
if (lockObtained)
{
// Find the value, and then remove the target from our collection
if (_optimisticCount > 0)
{
for (int index = 0; index <= _lastItemIndex; ++index)
{
if (value == _items[index].Target)
{ // checks tag & gets target
_items[index].RemoveTarget();
_optimisticCount--;
break;
}
}
}
}
}
finally
{
ExitItemLockIfNeeded(lockObtained);
}
}
// This is polling lock that will abandon getting the lock if _isNotifying is set to true
private void TryEnterItemLock(ref bool lockObtained)
{
// Assume that we couldn't take the lock
lockObtained = false;
// Keep trying to take the lock until either we've taken it, or the collection is being notified
while ((!_isNotifying) && (!lockObtained))
{
Monitor.TryEnter(_itemLock, LockPollTime, ref lockObtained);
}
}
private void ExitItemLockIfNeeded(bool lockObtained)
{
if (lockObtained)
{
Monitor.Exit(_itemLock);
}
}
}
}
|