forked from IronLanguages/main
/
WeakDictionary.cs
412 lines (342 loc) · 14.3 KB
/
WeakDictionary.cs
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation.
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
* copy of the license can be found in the License.html file at the root of this distribution. If
* you cannot locate the Apache License, Version 2.0, please send an email to
* dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
* by the terms of the Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*
*
* ***************************************************************************/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Microsoft.Scripting.Utils {
/// <summary>
/// Similar to Dictionary[TKey,TValue], but it also ensures that the keys will not be kept alive
/// if the only reference is from this collection. The value will be kept alive as long as the key
/// is alive.
///
/// This currently has a limitation that the caller is responsible for ensuring that an object used as
/// a key is not also used as a value in *any* instance of a WeakHash. Otherwise, it will result in the
/// object being kept alive forever. This effectively means that the owner of the WeakHash should be the
/// only one who has access to the object used as a value.
///
/// Currently, there is also no guarantee of how long the values will be kept alive even after the keys
/// get collected. This could be fixed by triggerring CheckCleanup() to be called on every garbage-collection
/// by having a dummy watch-dog object with a finalizer which calls CheckCleanup().
/// </summary>
public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue> {
// The one and only comparer instance.
static readonly IEqualityComparer<object> comparer = new WeakComparer<object>();
IDictionary<object, TValue> dict = new Dictionary<object, TValue>(comparer);
int version, cleanupVersion;
#if SILVERLIGHT || WIN8 || WP75 // GC
WeakReference cleanupGC = new WeakReference(new object());
#else
int cleanupGC = 0;
#endif
public WeakDictionary() {
}
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value) {
CheckCleanup();
// If the WeakHash already holds this value as a key, it will lead to a circular-reference and result
// in the objects being kept alive forever. The caller needs to ensure that this cannot happen.
Debug.Assert(!dict.ContainsKey(value));
dict.Add(new WeakObject(key), value);
}
public bool ContainsKey(TKey key) {
// We dont have to worry about creating "new WeakObject(key)" since the comparer
// can compare raw objects with WeakObject.
return dict.ContainsKey(key);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] // TODO: fix
public ICollection<TKey> Keys {
get {
// TODO:
throw new NotImplementedException();
}
}
public bool Remove(TKey key) {
return dict.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value) {
return dict.TryGetValue(key, out value);
}
public TValue GetOrCreateValue(TKey key) {
TValue value;
if (!TryGetValue(key, out value)) {
var ctor = typeof(TValue).GetConstructor(new Type[] { });
if (ctor == null) {
throw new MissingMethodException(string.Format("{0} does not have a default constructor.", typeof(TValue).Name));
}
value = (TValue)ctor.Invoke(new object[] { });
Add(key, value);
}
return value;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] // TODO: fix
public ICollection<TValue> Values {
get {
// TODO:
throw new NotImplementedException();
}
}
public TValue this[TKey key] {
get {
return dict[key];
}
set {
// If the WeakHash already holds this value as a key, it will lead to a circular-reference and result
// in the objects being kept alive forever. The caller needs to ensure that this cannot happen.
Debug.Assert(!dict.ContainsKey(value));
CheckCleanup();
dict[new WeakObject(key)] = value;
}
}
/// <summary>
/// Check if any of the keys have gotten collected
///
/// Currently, there is also no guarantee of how long the values will be kept alive even after the keys
/// get collected. This could be fixed by triggerring CheckCleanup() to be called on every garbage-collection
/// by having a dummy watch-dog object with a finalizer which calls CheckCleanup().
/// </summary>
void CheckCleanup() {
version++;
long change = version - cleanupVersion;
// Cleanup the table if it is a while since we have done it last time.
// Take the size of the table into account.
if (change > 1234 + dict.Count / 2) {
// It makes sense to do the cleanup only if a GC has happened in the meantime.
// WeakReferences can become zero only during the GC.
bool garbage_collected;
#if SILVERLIGHT || WIN8 || WP75 // GC.CollectionCount
garbage_collected = !cleanupGC.IsAlive;
if (garbage_collected) {
cleanupGC = new WeakReference(new object());
}
#else
int currentGC = GC.CollectionCount(0);
garbage_collected = currentGC != cleanupGC;
if (garbage_collected) cleanupGC = currentGC;
#endif
if (garbage_collected) {
Cleanup();
cleanupVersion = version;
} else {
cleanupVersion += 1234;
}
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]
private void Cleanup() {
int liveCount = 0;
int emptyCount = 0;
foreach (WeakObject w in dict.Keys) {
if (w.Target != null)
liveCount++;
else
emptyCount++;
}
// Rehash the table if there is a significant number of empty slots
if (emptyCount > liveCount / 4) {
Dictionary<object, TValue> newtable = new Dictionary<object, TValue>(liveCount + liveCount / 4, comparer);
foreach (WeakObject w in dict.Keys) {
object target = w.Target;
if (target != null)
newtable[w] = dict[w];
GC.KeepAlive(target);
}
dict = newtable;
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item) {
// TODO:
throw new NotImplementedException();
}
public void Clear() {
// TODO:
throw new NotImplementedException();
}
public bool Contains(KeyValuePair<TKey, TValue> item) {
// TODO:
throw new NotImplementedException();
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
// TODO:
throw new NotImplementedException();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] // TODO: fix
public int Count {
get {
// TODO:
throw new NotImplementedException();
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] // TODO: fix
public bool IsReadOnly {
get {
// TODO:
throw new NotImplementedException();
}
}
public bool Remove(KeyValuePair<TKey, TValue> item) {
// TODO:
throw new NotImplementedException();
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
// TODO:
throw new NotImplementedException();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
// TODO:
throw new NotImplementedException();
}
#endregion
}
internal class WeakObject {
WeakReference weakReference;
int hashCode;
public WeakObject(object obj) {
// CF throws doesn't support long weak references (NotSuportedException is thrown)
weakReference = new WeakReference(obj, !PlatformAdaptationLayer.IsCompactFramework);
hashCode = (obj == null) ? 0 : ReferenceEqualityComparer<object>.Instance.GetHashCode(obj);
}
public object Target {
get {
return weakReference.Target;
}
}
public override int GetHashCode() {
return hashCode;
}
public override bool Equals(object obj) {
object target = weakReference.Target;
if (target == null) {
return false;
}
return target.Equals(obj);
}
}
// WeakComparer treats WeakObject as transparent envelope
sealed class WeakComparer<T> : IEqualityComparer<T> {
bool IEqualityComparer<T>.Equals(T x, T y) {
WeakObject wx = x as WeakObject;
if (wx != null)
x = (T)wx.Target;
WeakObject wy = y as WeakObject;
if (wy != null)
y = (T)wy.Target;
return Object.Equals(x, y);
}
int IEqualityComparer<T>.GetHashCode(T obj) {
WeakObject wobj = obj as WeakObject;
if (wobj != null)
return wobj.GetHashCode();
return (obj == null) ? 0 : ReferenceEqualityComparer<object>.Instance.GetHashCode(obj);
}
}
public sealed class HybridMapping<T> {
private Dictionary<int, object> _dict = new Dictionary<int, object>();
private readonly Object _synchObject = new Object();
private readonly int _offset;
private int _current;
private const int SIZE = 4096;
private const int MIN_RANGE = SIZE / 2;
public HybridMapping()
: this(0) {
}
public HybridMapping(int offset) {
if (offset < 0 || (SIZE - offset) < MIN_RANGE) {
throw new InvalidOperationException("HybridMapping is full");
}
_offset = offset;
_current = offset;
}
private void NextKey() {
if (++_current >= SIZE) {
_current = _offset;
}
}
public int WeakAdd(T value) {
lock (_synchObject) {
int saved = _current;
while (_dict.ContainsKey(_current)) {
NextKey();
if (_current == saved)
throw new InvalidOperationException("HybridMapping is full");
}
_dict.Add(_current, new WeakObject(value));
return _current;
}
}
public int StrongAdd(T value) {
lock (_synchObject) {
int saved = _current;
while (_dict.ContainsKey(_current)) {
NextKey();
if (_current == saved)
throw new InvalidOperationException("HybridMapping is full");
}
_dict.Add(_current, value);
return _current;
}
}
public T GetObjectFromId(int id) {
object ret;
if (_dict.TryGetValue(id, out ret)) {
WeakObject weakObj = ret as WeakObject;
if (weakObj != null) {
return (T)weakObj.Target;
}
if (ret is T) {
return (T)ret;
}
throw new InvalidOperationException("Unexpected dictionary content: type " + ret.GetType());
} else
return default(T);
}
public int GetIdFromObject(T value) {
lock (_synchObject) {
foreach (KeyValuePair<int, object> kv in _dict) {
if (kv.Value is WeakObject) {
object target = ((WeakObject)kv.Value).Target;
if (target != null && target.Equals(value))
return kv.Key;
} else if (kv.Value is T) {
object target = (T)(kv.Value);
if (target.Equals(value))
return kv.Key;
}
}
}
return -1;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] // TODO: fix (rename?)
public void RemoveOnId(int id) {
lock (_synchObject) {
_dict.Remove(id);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] // TODO: fix
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] // TODO: fix (rename?)
public void RemoveOnObject(T value) {
try {
int id = GetIdFromObject(value);
RemoveOnId(id);
} catch { }
}
}
}