Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data / System / Data / ProviderBase / DbReferenceCollection.cs
blob1f55090b42ae95c77c05c6e8f675af35bc5a7717
1 //------------------------------------------------------------------------------
2 // <copyright file="DbReferenceCollection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data.ProviderBase {
11 using System;
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.Diagnostics;
15 using System.Threading;
17 internal abstract class DbReferenceCollection {
19 private struct CollectionEntry {
20 private int _tag; // information about the reference
21 private WeakReference _weak; // the reference itself.
23 public void NewTarget(int tag, object target) {
24 Debug.Assert(!HasTarget, "Entry already has a valid target");
25 Debug.Assert(tag != 0, "Bad tag");
26 Debug.Assert(target != null, "Invalid target");
28 if (_weak == null) {
29 _weak = new WeakReference(target, false);
31 else {
32 _weak.Target = target;
34 _tag = tag;
37 public void RemoveTarget() {
38 _tag = 0;
41 public bool HasTarget {
42 get {
43 return ((_tag != 0) && (_weak.IsAlive));
47 public int Tag {
48 get {
49 return _tag;
53 public object Target {
54 get {
55 return (_tag == 0 ? null : _weak.Target);
60 private const int LockPollTime = 100; // Time to wait (in ms) between attempting to get the _itemLock
61 private const int DefaultCollectionSize = 20; // Default size for the collection, and the amount to grow everytime the collection is full
62 private CollectionEntry[] _items; // The collection of items we are keeping track of
63 private readonly object _itemLock; // Used to synchronize access to the _items collection
64 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)
65 private int _lastItemIndex; // Location of the last item in _items
66 private volatile bool _isNotifying; // Indicates that the collection is currently being notified (and, therefore, about to be cleared)
68 protected DbReferenceCollection() {
69 _items = new CollectionEntry[DefaultCollectionSize];
70 _itemLock = new object();
71 _optimisticCount = 0;
72 _lastItemIndex = 0;
75 abstract public void Add(object value, int tag);
77 protected void AddItem(object value, int tag) {
78 Debug.Assert(null != value && 0 != tag, "AddItem with null value or 0 tag");
79 bool itemAdded = false;
81 lock (_itemLock) {
82 // Try to find a free spot
83 for (int i = 0; i <= _lastItemIndex; ++i) {
84 if (_items[i].Tag == 0) {
85 _items[i].NewTarget(tag, value);
86 Debug.Assert(_items[i].HasTarget, "missing expected target");
87 itemAdded = true;
88 break;
92 // No free spots, can we just add on to the end?
93 if ((!itemAdded) && (_lastItemIndex + 1 < _items.Length)) {
94 _lastItemIndex++;
95 _items[_lastItemIndex].NewTarget(tag, value);
96 itemAdded = true;
99 // If no free spots and no space at the end, try to find a dead item
100 if (!itemAdded) {
101 for (int i = 0; i <= _lastItemIndex; ++i) {
102 if (!_items[i].HasTarget) {
103 _items[i].NewTarget(tag, value);
104 Debug.Assert(_items[i].HasTarget, "missing expected target");
105 itemAdded = true;
106 break;
111 // If nothing was free, then resize and add to the end
112 if (!itemAdded) {
113 Array.Resize<CollectionEntry>(ref _items, _items.Length * 2);
114 _lastItemIndex++;
115 _items[_lastItemIndex].NewTarget(tag, value);
118 _optimisticCount++;
122 internal T FindItem<T>(int tag, Func<T, bool> filterMethod) where T : class {
123 bool lockObtained = false;
124 try {
125 TryEnterItemLock(ref lockObtained);
126 if (lockObtained) {
127 if (_optimisticCount > 0) {
128 // Loop through the items
129 for (int counter = 0; counter <= _lastItemIndex; counter++) {
130 // Check tag (should be easiest and quickest)
131 if (_items[counter].Tag == tag) {
132 // NOTE: Check if the returned value is null twice may seem wasteful, but this if for performance
133 // Since checking for null twice is cheaper than calling both HasTarget and Target OR always attempting to typecast
134 object value = _items[counter].Target;
135 if (value != null) {
136 // Make sure the item has the correct type and passes the filtering
137 T tempItem = value as T;
138 if ((tempItem != null) && (filterMethod(tempItem))) {
139 return tempItem;
147 finally {
148 ExitItemLockIfNeeded(lockObtained);
151 // If we got to here, then no item was found, so return null
152 return null;
155 public void Notify(int message) {
156 bool lockObtained = false;
157 try {
158 TryEnterItemLock(ref lockObtained);
159 if (lockObtained) {
160 try {
161 _isNotifying = true;
163 // Loop through each live item and notify it
164 if (_optimisticCount > 0) {
165 for (int index = 0; index <= _lastItemIndex; ++index) {
166 object value = _items[index].Target; // checks tag & gets target
167 if (null != value) {
168 NotifyItem(message, _items[index].Tag, value);
169 _items[index].RemoveTarget();
171 Debug.Assert(!_items[index].HasTarget, "Unexpected target after notifying");
173 _optimisticCount = 0;
176 // Shrink collection (if needed)
177 if (_items.Length > 100) {
178 _lastItemIndex = 0;
179 _items = new CollectionEntry[DefaultCollectionSize];
182 finally {
183 _isNotifying = false;
187 finally {
188 ExitItemLockIfNeeded(lockObtained);
192 abstract protected void NotifyItem(int message, int tag, object value);
194 abstract public void Remove(object value);
196 protected void RemoveItem(object value) {
197 Debug.Assert(null != value, "RemoveItem with null");
199 bool lockObtained = false;
200 try {
201 TryEnterItemLock(ref lockObtained);
203 if (lockObtained) {
204 // Find the value, and then remove the target from our collection
205 if (_optimisticCount > 0) {
206 for (int index = 0; index <= _lastItemIndex; ++index) {
207 if (value == _items[index].Target) { // checks tag & gets target
208 _items[index].RemoveTarget();
209 _optimisticCount--;
210 break;
216 finally {
217 ExitItemLockIfNeeded(lockObtained);
221 // This is polling lock that will abandon getting the lock if _isNotifying is set to true
222 private void TryEnterItemLock(ref bool lockObtained) {
223 // Assume that we couldn't take the lock
224 lockObtained = false;
225 // Keep trying to take the lock until either we've taken it, or the collection is being notified
226 while ((!_isNotifying) && (!lockObtained)) {
227 Monitor.TryEnter(_itemLock, LockPollTime, ref lockObtained);
231 private void ExitItemLockIfNeeded(bool lockObtained) {
232 if (lockObtained) {
233 Monitor.Exit(_itemLock);