Welcome to mirror list, hosted at ThFree Co, Russian Federation.

DbReferenceCollection.cs « ProviderBase « Data « System « System.Data « referencesource « class « mcs - github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 1f55090b42ae95c77c05c6e8f675af35bc5a7717 (plain)
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);
            }
        }
    }
}