Skip to content

Commit adbb8f7

Browse files
monojenkinsluhenry
authored andcommitted
[2018-06] Implement IEnumerable for ConditionalWeakTable (#10657)
* Implement IEnumerable for ConditionalWeakTable * Update ConditionalWeakTable.cs
1 parent 1b18f39 commit adbb8f7

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

mcs/class/corlib/System.Runtime.CompilerServices/ConditionalWeakTable.cs

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
using System;
3232
using System.Collections;
3333
using System.Collections.Generic;
34+
using System.Diagnostics;
35+
using System.Threading;
3436

3537
namespace System.Runtime.CompilerServices
3638
{
@@ -375,11 +377,109 @@ internal ICollection<TValue> Values
375377
}
376378
}
377379

380+
// IEnumerable implementation was copied from CoreCLR
378381
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator ()
379382
{
380-
throw new NotImplementedException ();
383+
lock (_lock)
384+
{
385+
return size == 0 ?
386+
((IEnumerable<KeyValuePair<TKey, TValue>>)Array.Empty<KeyValuePair<TKey, TValue>>()).GetEnumerator() :
387+
new Enumerator(this);
388+
}
381389
}
382390

383391
IEnumerator IEnumerable.GetEnumerator () => ((IEnumerable<KeyValuePair<TKey, TValue>>)this).GetEnumerator ();
392+
393+
/// <summary>Provides an enumerator for the table.</summary>
394+
private sealed class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
395+
{
396+
// The enumerator would ideally hold a reference to the Container and the end index within that
397+
// container. However, the safety of the CWT depends on the only reference to the Container being
398+
// from the CWT itself; the Container then employs a two-phase finalization scheme, where the first
399+
// phase nulls out that parent CWT's reference, guaranteeing that the second time it's finalized there
400+
// can be no other existing references to it in use that would allow for concurrent usage of the
401+
// native handles with finalization. We would break that if we allowed this Enumerator to hold a
402+
// reference to the Container. Instead, the Enumerator holds a reference to the CWT rather than to
403+
// the Container, and it maintains the CWT._activeEnumeratorRefCount field to track whether there
404+
// are outstanding enumerators that have yet to be disposed/finalized. If there aren't any, the CWT
405+
// behaves as it normally does. If there are, certain operations are affected, in particular resizes.
406+
// Normally when the CWT is resized, it enumerates the contents of the table looking for indices that
407+
// contain entries which have been collected or removed, and it frees those up, effectively moving
408+
// down all subsequent entries in the container (not in the existing container, but in a replacement).
409+
// This, however, would cause the enumerator's understanding of indices to break. So, as long as
410+
// there is any outstanding enumerator, no compaction is performed.
411+
412+
private ConditionalWeakTable<TKey, TValue> _table; // parent table, set to null when disposed
413+
private int _currentIndex = -1; // the current index into the container
414+
private KeyValuePair<TKey, TValue> _current; // the current entry set by MoveNext and returned from Current
415+
416+
public Enumerator(ConditionalWeakTable<TKey, TValue> table)
417+
{
418+
// Store a reference to the parent table and increase its active enumerator count.
419+
_table = table;
420+
_currentIndex = -1;
421+
}
422+
423+
~Enumerator() { Dispose(); }
424+
425+
public void Dispose()
426+
{
427+
// Use an interlocked operation to ensure that only one thread can get access to
428+
// the _table for disposal and thus only decrement the ref count once.
429+
ConditionalWeakTable<TKey, TValue> table = Interlocked.Exchange(ref _table, null);
430+
if (table != null)
431+
{
432+
// Ensure we don't keep the last current alive unnecessarily
433+
_current = default;
434+
435+
// Finalization is purely to decrement the ref count. We can suppress it now.
436+
GC.SuppressFinalize(this);
437+
}
438+
}
439+
440+
public bool MoveNext()
441+
{
442+
// Start by getting the current table. If it's already been disposed, it will be null.
443+
ConditionalWeakTable<TKey, TValue> table = _table;
444+
if (table != null)
445+
{
446+
// Once have the table, we need to lock to synchronize with other operations on
447+
// the table, like adding.
448+
lock (table._lock)
449+
{
450+
var tombstone = GC.EPHEMERON_TOMBSTONE;
451+
while (_currentIndex < table.data.Length - 1)
452+
{
453+
_currentIndex++;
454+
var currentDataItem = table.data[_currentIndex];
455+
if (currentDataItem.key != null && currentDataItem.key != tombstone)
456+
{
457+
_current = new KeyValuePair<TKey, TValue>((TKey)currentDataItem.key, (TValue)currentDataItem.value);
458+
return true;
459+
}
460+
}
461+
}
462+
}
463+
464+
// Nothing more to enumerate.
465+
return false;
466+
}
467+
468+
public KeyValuePair<TKey, TValue> Current
469+
{
470+
get
471+
{
472+
if (_currentIndex < 0)
473+
{
474+
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
475+
}
476+
return _current;
477+
}
478+
}
479+
480+
object IEnumerator.Current => Current;
481+
482+
public void Reset() { }
483+
}
384484
}
385485
}

mcs/class/corlib/Test/System.Runtime.CompilerServices/ConditionalWeakTableTest.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
using NUnit.Framework;
3131
using System;
32+
using System.Linq;
3233
using System.Reflection;
3334
using System.Runtime.CompilerServices;
3435
using System.Runtime.InteropServices;
@@ -486,6 +487,20 @@ public void OldGenKeysMakeNewGenObjectsReachable ()
486487
Assert.IsTrue (table.Remove (key), "#2-" + i + "-k-" + key);
487488
}
488489
}
490+
491+
[Test]
492+
public void ConditionalWeakTableEnumerable()
493+
{
494+
var cwt = new ConditionalWeakTable<string, string>();
495+
Assert.AreEqual(0, cwt.ToArray().Length);
496+
cwt.Add("test1", "foo1");
497+
cwt.Add("test2", "foo2");
498+
Assert.AreEqual(2, cwt.ToArray().Length);
499+
cwt.Remove("test1");
500+
Assert.AreEqual(1, cwt.ToArray().Length);
501+
cwt.Remove("test2");
502+
Assert.AreEqual(0, cwt.ToArray().Length);
503+
}
489504
}
490505
}
491506

0 commit comments

Comments
 (0)