|
//------------------------------------------------------------------------------
// <copyright file="DbReferenceCollection.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
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<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
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);
}
}
}
}
|