//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft // Microsoft //------------------------------------------------------------------------------ namespace System.Data.ProviderBase { using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Threading; 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 everytime 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; } abstract public 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(ref _items, _items.Length * 2); _lastItemIndex++; _items[_lastItemIndex].NewTarget(tag, value); } _optimisticCount++; } } internal T FindItem(int tag, Func 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 T tempItem = value as T; if ((tempItem != null) && (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); } } abstract protected void NotifyItem(int message, int tag, object value); abstract public 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); } } } }