1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
|
//------------------------------------------------------------------------------
// <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);
}
}
}
}
|