PageRenderTime 1ms CodeModel.GetById 18ms app.highlight 7ms RepoModel.GetById 0ms app.codeStats 0ms

/IronPython_2_0/Src/Microsoft.Scripting/Utils/WeakDictionary.cs

#
C# | 397 lines | 272 code | 66 blank | 59 comment | 41 complexity | fdf51a50a590f4fabbfc585a5c197e0c MD5 | raw file
Possible License(s): GPL-2.0, MPL-2.0-no-copyleft-exception, CPL-1.0, CC-BY-SA-3.0, BSD-3-Clause, ISC, AGPL-3.0, LGPL-2.1, Apache-2.0
  1/* ****************************************************************************
  2 *
  3 * Copyright (c) Microsoft Corporation. 
  4 *
  5 * This source code is subject to terms and conditions of the Microsoft Public License. A 
  6 * copy of the license can be found in the License.html file at the root of this distribution. If 
  7 * you cannot locate the  Microsoft Public License, please send an email to 
  8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
  9 * by the terms of the Microsoft Public License.
 10 *
 11 * You must not remove this notice, or any other, from this software.
 12 *
 13 *
 14 * ***************************************************************************/
 15
 16using System; using Microsoft;
 17using System.Collections.Generic;
 18using System.Diagnostics;
 19using Microsoft.Contracts;
 20
 21namespace Microsoft.Scripting.Utils {
 22    /// <summary>
 23    /// Similar to Dictionary[TKey,TValue], but it also ensures that the keys will not be kept alive
 24    /// if the only reference is from this collection. The value will be kept alive as long as the key
 25    /// is alive.
 26    /// 
 27    /// This currently has a limitation that the caller is responsible for ensuring that an object used as 
 28    /// a key is not also used as a value in *any* instance of a WeakHash. Otherwise, it will result in the
 29    /// object being kept alive forever. This effectively means that the owner of the WeakHash should be the
 30    /// only one who has access to the object used as a value.
 31    /// 
 32    /// Currently, there is also no guarantee of how long the values will be kept alive even after the keys
 33    /// get collected. This could be fixed by triggerring CheckCleanup() to be called on every garbage-collection
 34    /// by having a dummy watch-dog object with a finalizer which calls CheckCleanup().
 35    /// </summary>
 36    public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue> {
 37        // The one and only comparer instance.
 38        static readonly IEqualityComparer<object> comparer = new WeakComparer<object>();
 39
 40        IDictionary<object, TValue> dict = new Dictionary<object, TValue>(comparer);
 41        int version, cleanupVersion;
 42
 43#if SILVERLIGHT // GC
 44        WeakReference cleanupGC = new WeakReference(new object());
 45#else
 46        int cleanupGC = 0;
 47#endif
 48
 49        public WeakDictionary() {
 50        }
 51
 52        #region IDictionary<TKey,TValue> Members
 53
 54        public void Add(TKey key, TValue value) {
 55            CheckCleanup();
 56
 57            // If the WeakHash already holds this value as a key, it will lead to a circular-reference and result
 58            // in the objects being kept alive forever. The caller needs to ensure that this cannot happen.
 59            Debug.Assert(!dict.ContainsKey(value));
 60
 61            dict.Add(new WeakObject<TKey>(key), value);
 62        }
 63
 64        [Confined]
 65        public bool ContainsKey(TKey key) {
 66            // We dont have to worry about creating "new WeakObject<TKey>(key)" since the comparer
 67            // can compare raw objects with WeakObject<T>.
 68            return dict.ContainsKey(key);
 69        }
 70
 71        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] // TODO: fix
 72        public ICollection<TKey> Keys {
 73            get {
 74                // TODO:
 75                throw new NotImplementedException();
 76            }
 77        }
 78
 79        public bool Remove(TKey key) {
 80            return dict.Remove(key);
 81        }
 82
 83        public bool TryGetValue(TKey key, out TValue value) {
 84            return dict.TryGetValue(key, out value);
 85        }
 86
 87        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] // TODO: fix
 88        public ICollection<TValue> Values {
 89            get {
 90                // TODO:
 91                throw new NotImplementedException();
 92            }
 93        }
 94
 95        public TValue this[TKey key] {
 96            get {
 97                return dict[key];
 98            }
 99            set {
100                // If the WeakHash already holds this value as a key, it will lead to a circular-reference and result
101                // in the objects being kept alive forever. The caller needs to ensure that this cannot happen.
102                Debug.Assert(!dict.ContainsKey(value));
103
104                dict[new WeakObject<TKey>(key)] = value;
105            }
106        }
107
108        /// <summary>
109        /// Check if any of the keys have gotten collected
110        /// 
111        /// Currently, there is also no guarantee of how long the values will be kept alive even after the keys
112        /// get collected. This could be fixed by triggerring CheckCleanup() to be called on every garbage-collection
113        /// by having a dummy watch-dog object with a finalizer which calls CheckCleanup().
114        /// </summary>
115        void CheckCleanup() {
116            version++;
117
118            long change = version - cleanupVersion;
119
120            // Cleanup the table if it is a while since we have done it last time.
121            // Take the size of the table into account.
122            if (change > 1234 + dict.Count / 2) {
123                // It makes sense to do the cleanup only if a GC has happened in the meantime.
124                // WeakReferences can become zero only during the GC.
125
126                bool garbage_collected;
127#if SILVERLIGHT // GC.CollectionCount
128                garbage_collected = !cleanupGC.IsAlive;
129                if (garbage_collected) cleanupGC = new WeakReference(new object());
130#else
131                int currentGC = GC.CollectionCount(0);
132                garbage_collected = currentGC != cleanupGC;
133                if (garbage_collected) cleanupGC = currentGC;
134#endif
135                if (garbage_collected) {
136                    Cleanup();
137                    cleanupVersion = version;
138                } else {
139                    cleanupVersion += 1234;
140                }
141            }
142        }
143
144        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]
145        private void Cleanup() {
146
147            int liveCount = 0;
148            int emptyCount = 0;
149
150            foreach (WeakObject<TKey> w in dict.Keys) {
151                if (w.Target != null)
152                    liveCount++;
153                else
154                    emptyCount++;
155            }
156
157            // Rehash the table if there is a significant number of empty slots
158            if (emptyCount > liveCount / 4) {
159                Dictionary<object, TValue> newtable = new Dictionary<object, TValue>(liveCount + liveCount / 4, comparer);
160
161                foreach (WeakObject<TKey> w in dict.Keys) {
162                    object target = w.Target;
163
164                    if (target != null)
165                        newtable[w] = dict[w];
166
167                    GC.KeepAlive(target);
168                }
169
170                dict = newtable;
171            }
172        }
173        #endregion
174
175        #region ICollection<KeyValuePair<TKey,TValue>> Members
176
177        public void Add(KeyValuePair<TKey, TValue> item) {
178            // TODO:
179            throw new NotImplementedException();
180        }
181
182        public void Clear() {
183            // TODO:
184            throw new NotImplementedException();
185        }
186
187        [Confined]
188        public bool Contains(KeyValuePair<TKey, TValue> item) {
189            // TODO:
190            throw new NotImplementedException();
191        }
192
193        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
194            // TODO:
195            throw new NotImplementedException();
196        }
197
198        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] // TODO: fix
199        public int Count {
200            get {
201                // TODO:
202                throw new NotImplementedException();
203            }
204        }
205
206        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] // TODO: fix
207        public bool IsReadOnly {
208            get {
209                // TODO:
210                throw new NotImplementedException();
211            }
212        }
213
214        public bool Remove(KeyValuePair<TKey, TValue> item) {
215            // TODO:
216            throw new NotImplementedException();
217        }
218
219        #endregion
220
221        #region IEnumerable<KeyValuePair<TKey,TValue>> Members
222
223        [Pure]
224        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
225            // TODO:
226            throw new NotImplementedException();
227        }
228
229        #endregion
230
231        #region IEnumerable Members
232
233        [Pure]
234        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
235            // TODO:
236            throw new NotImplementedException();
237        }
238
239        #endregion
240    }
241
242    internal class WeakObject<T> {
243        WeakReference weakReference;
244        int hashCode;
245
246        public WeakObject(T obj) {
247            weakReference = new WeakReference(obj, true);
248            hashCode = (obj == null) ? 0 : obj.GetHashCode();
249        }
250
251        public T Target {
252            get {
253                return (T)weakReference.Target;
254            }
255        }
256
257        [Confined]
258        public override int GetHashCode() {
259            return hashCode;
260        }
261
262        [Confined]
263        public override bool Equals(object obj) {
264            object target = weakReference.Target;
265            if (target == null) {
266                return false;
267            }
268
269            return ((T)target).Equals(obj);
270        }
271    }
272
273    // WeakComparer treats WeakObject as transparent envelope
274    sealed class WeakComparer<T> : IEqualityComparer<T> {
275        bool IEqualityComparer<T>.Equals(T x, T y) {
276            WeakObject<T> wx = x as WeakObject<T>;
277            if (wx != null)
278                x = wx.Target;
279
280            WeakObject<T> wy = y as WeakObject<T>;
281            if (wy != null)
282                y = wy.Target;
283
284            return Object.Equals(x, y);
285        }
286
287        int IEqualityComparer<T>.GetHashCode(T obj) {
288            WeakObject<T> wobj = obj as WeakObject<T>;
289            if (wobj != null)
290                return wobj.GetHashCode();
291
292            return (obj == null) ? 0 : obj.GetHashCode();
293        }
294    }
295
296    public sealed class HybridMapping<T> {
297        private Dictionary<int, object> _dict = new Dictionary<int, object>();
298        private readonly Object _synchObject = new Object();
299        private readonly int _offset;
300        private int _current;
301
302        private const int SIZE = 4096;
303        private const int MIN_RANGE = SIZE / 2;
304
305        public HybridMapping() : this(0) {
306        }
307
308        public HybridMapping(int offset) {
309            if (offset < 0 || (SIZE - offset) < MIN_RANGE) {
310                throw new InvalidOperationException("HybridMapping is full");
311            }
312            _offset = offset;
313            _current = offset;
314        }
315
316        private void NextKey() {
317            if (++_current >= SIZE) {
318                _current = _offset;
319            }
320        }
321
322        public int WeakAdd(T value) {
323            lock (_synchObject) {
324                int saved = _current;
325                while (_dict.ContainsKey(_current)) {
326                    NextKey();
327                    if (_current == saved)
328                        throw new InvalidOperationException("HybridMapping is full");
329                }
330                _dict.Add(_current, new WeakObject<T>(value));
331                return _current;
332            }
333        }
334
335        public int StrongAdd(T value) {
336            lock (_synchObject) {
337                int saved = _current;
338                while (_dict.ContainsKey(_current)) {
339                    NextKey();
340                    if (_current == saved)
341                        throw new InvalidOperationException("HybridMapping is full");
342                }
343                _dict.Add(_current, value);
344                return _current;
345            }
346        }
347
348        public T GetObjectFromId(int id) {
349            object ret;
350            if (_dict.TryGetValue(id, out ret)) {
351                WeakObject<T> weakObj = ret as WeakObject<T>;
352                if (weakObj != null) {
353                    return weakObj.Target;
354                }
355                if (ret is T) {
356                    return (T)ret;
357                }
358
359                throw new InvalidOperationException("Unexpected dictionary content: type " + ret.GetType());
360            } else
361                return default(T);
362        }
363
364        public int GetIdFromObject(T value) {
365            lock (_synchObject) {
366                foreach (KeyValuePair<int, object> kv in _dict) {
367                    if (kv.Value is WeakObject<T>) {
368                        object target = ((WeakObject<T>)kv.Value).Target;
369                        if (target != null && target.Equals(value))
370                            return kv.Key;
371                    } else if (kv.Value is T) {
372                        object target = (T)(kv.Value);
373                        if (target.Equals(value))
374                            return kv.Key;
375                    }
376                }
377            }
378            return -1;
379        }
380
381        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] // TODO: fix (rename?)
382        public void RemoveOnId(int id) {
383            lock (_synchObject) {
384                _dict.Remove(id);
385            }
386        }
387
388        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] // TODO: fix
389        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] // TODO: fix (rename?)
390        public void RemoveOnObject(T value) {
391            try {
392                int id = GetIdFromObject(value);
393                RemoveOnId(id);
394            } catch { }
395        }
396    }
397}