public async Task CollectDisposedSnapshots()
    {
        var d = new SnapDictionary <int, string>();

        d.Test.CollectAuto = false;

        // gen 1
        d.Set(1, "one");
        Assert.AreEqual(1, d.Test.GetValues(1).Length);

        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        var s1 = d.CreateSnapshot();

        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);

        // gen 2
        d.Set(1, "two");
        Assert.AreEqual(2, d.Test.GetValues(1).Length);

        Assert.AreEqual(2, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        var s2 = d.CreateSnapshot();

        Assert.AreEqual(2, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);

        // gen 3
        d.Set(1, "three");
        Assert.AreEqual(3, d.Test.GetValues(1).Length);

        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        var s3 = d.CreateSnapshot();

        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);

        Assert.AreEqual(3, d.SnapCount);

        s1.Dispose();
        await d.CollectAsync();

        Assert.AreEqual(2, d.SnapCount);
        Assert.AreEqual(2, d.Test.GetValues(1).Length);

        s2.Dispose();
        await d.CollectAsync();

        Assert.AreEqual(1, d.SnapCount);
        Assert.AreEqual(1, d.Test.GetValues(1).Length);

        s3.Dispose();
        await d.CollectAsync();

        Assert.AreEqual(0, d.SnapCount);
        Assert.AreEqual(1, d.Test.GetValues(1).Length);
    }
    public void WriteLocking2()
    {
        var d = new SnapDictionary <int, string>();

        d.Test.CollectAuto = false;

        // gen 1
        d.Set(1, "one");
        Assert.AreEqual(1, d.Test.GetValues(1).Length);

        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        var s1 = d.CreateSnapshot();

        Assert.AreEqual(1, s1.Gen);
        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);
        Assert.AreEqual("one", s1.Get(1));

        // gen 2
        Assert.AreEqual(1, d.Test.GetValues(1).Length);
        d.Set(1, "uno");
        Assert.AreEqual(2, d.Test.GetValues(1).Length);

        Assert.AreEqual(2, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        var s2 = d.CreateSnapshot();

        Assert.AreEqual(2, s2.Gen);
        Assert.AreEqual(2, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);
        Assert.AreEqual("uno", s2.Get(1));

        var scopeProvider = GetScopeProvider();

        using (d.GetScopedWriteLock(scopeProvider))
        {
            // gen 3
            Assert.AreEqual(2, d.Test.GetValues(1).Length);
            d.SetLocked(1, "ein");
            Assert.AreEqual(3, d.Test.GetValues(1).Length);

            Assert.AreEqual(3, d.Test.LiveGen);
            Assert.IsTrue(d.Test.NextGen);

            var s3 = d.CreateSnapshot();

            Assert.AreEqual(2, s3.Gen);
            Assert.AreEqual(3, d.Test.LiveGen);
            Assert.IsTrue(d.Test.NextGen); // has NOT changed when (non) creating snapshot
            Assert.AreEqual("uno", s3.Get(1));
        }

        var s4 = d.CreateSnapshot();

        Assert.AreEqual(3, s4.Gen);
        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);
        Assert.AreEqual("ein", s4.Get(1));
    }
    public async Task CollectNulls()
    {
        var d = new SnapDictionary <int, string>();

        d.Test.CollectAuto = false;

        // gen 1
        d.Set(1, "one");
        Assert.AreEqual(1, d.Test.GetValues(1).Length);
        d.Set(1, "one");
        Assert.AreEqual(1, d.Test.GetValues(1).Length);
        d.Set(1, "uno");
        Assert.AreEqual(1, d.Test.GetValues(1).Length);

        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        var s1 = d.CreateSnapshot();

        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);

        // gen 2
        Assert.AreEqual(1, d.Test.GetValues(1).Length);
        d.Set(1, "one");
        Assert.AreEqual(2, d.Test.GetValues(1).Length);
        d.Set(1, "uno");
        Assert.AreEqual(2, d.Test.GetValues(1).Length);

        Assert.AreEqual(2, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        var s2 = d.CreateSnapshot();

        Assert.AreEqual(2, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);

        // gen 3
        Assert.AreEqual(2, d.Test.GetValues(1).Length);
        d.Set(1, "one");
        Assert.AreEqual(3, d.Test.GetValues(1).Length);
        d.Set(1, "uno");
        Assert.AreEqual(3, d.Test.GetValues(1).Length);
        d.Clear(1);
        Assert.AreEqual(3, d.Test.GetValues(1).Length);

        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        var tv = d.Test.GetValues(1);

        Assert.AreEqual(3, tv[0].Gen);
        Assert.AreEqual(2, tv[1].Gen);
        Assert.AreEqual(1, tv[2].Gen);

        Assert.AreEqual(0, d.Test.FloorGen);

        // nothing to collect
        await d.CollectAsync();

        GC.KeepAlive(s1);
        GC.KeepAlive(s2);
        Assert.AreEqual(0, d.Test.FloorGen);
        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);
        Assert.AreEqual(2, d.SnapCount);
        Assert.AreEqual(3, d.Test.GetValues(1).Length);

        // one snapshot to collect
        s1 = null;
        GC.Collect();
        GC.KeepAlive(s2);
        await d.CollectAsync();

        Assert.AreEqual(1, d.Test.FloorGen);
        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);
        Assert.AreEqual(1, d.SnapCount);
        Assert.AreEqual(2, d.Test.GetValues(1).Length);

        // another snapshot to collect
        s2 = null;
        GC.Collect();
        await d.CollectAsync();

        Assert.AreEqual(2, d.Test.FloorGen);
        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);
        Assert.AreEqual(0, d.SnapCount);

        // and everything is gone?
        // no, cannot collect the live gen because we'd need to lock
        Assert.AreEqual(1, d.Test.GetValues(1).Length);

        d.CreateSnapshot();
        GC.Collect();
        await d.CollectAsync();

        // poof, gone
        Assert.AreEqual(0, d.Test.GetValues(1).Length);
    }
    [Retry(5)] // TODO make this test non-flaky.
    public async Task EventuallyCollectNulls()
    {
        var d = new SnapDictionary <int, string>();

        d.Test.CollectAuto = false;

        Assert.AreEqual(0, d.Test.GetValues(1).Length);

        // gen 1
        d.Set(1, "one");
        Assert.AreEqual(1, d.Test.GetValues(1).Length);

        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        await d.CollectAsync();

        var tv = d.Test.GetValues(1);

        Assert.AreEqual(1, tv.Length);
        Assert.AreEqual(1, tv[0].Gen);

        var s = d.CreateSnapshot();

        Assert.AreEqual("one", s.Get(1));

        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);

        Assert.AreEqual(1, d.Count);
        Assert.AreEqual(1, d.SnapCount);
        Assert.AreEqual(1, d.GenCount);

        // gen 2
        d.Clear(1);
        tv = d.Test.GetValues(1);
        Assert.AreEqual(2, tv.Length);
        Assert.AreEqual(2, tv[0].Gen);

        Assert.AreEqual(2, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        Assert.AreEqual(1, d.Count);
        Assert.AreEqual(1, d.SnapCount);
        Assert.AreEqual(1, d.GenCount);

        // nothing to collect
        await d.CollectAsync();

        GC.KeepAlive(s);
        Assert.AreEqual(2, d.Test.GetValues(1).Length);

        Assert.AreEqual(1, d.Count);
        Assert.AreEqual(1, d.SnapCount);
        Assert.AreEqual(1, d.GenCount);

        Assert.AreEqual(2, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        // collect snapshot
        // don't collect liveGen+
        s = null;     // without being disposed
        GC.Collect(); // should release the generation reference
        await d.CollectAsync();

        Assert.AreEqual(1, d.Test.GetValues(1).Length); // "one" value is gone
        Assert.AreEqual(1, d.Count);                    // still have 1 item
        Assert.AreEqual(0, d.SnapCount);                // snapshot is gone
        Assert.AreEqual(0, d.GenCount);                 // and generation has been dequeued

        // liveGen/nextGen
        s = d.CreateSnapshot();
        s = null;

        // collect liveGen
        GC.Collect();

        Assert.IsTrue(d.Test.GenObjs.TryPeek(out var genObj));
        genObj = null;

        // in Release mode, it works, but in Debug mode, the weak reference is still alive
        // and for some reason we need to do this to ensure it is collected
#if DEBUG
        await d.CollectAsync();

        GC.Collect();
#endif

        Assert.IsTrue(d.Test.GenObjs.TryPeek(out genObj));
        Assert.IsFalse(genObj.WeakGenRef.IsAlive); // snapshot is gone, along with its reference

        await d.CollectAsync();

        Assert.AreEqual(0, d.Test.GetValues(1).Length); // null value is gone
        Assert.AreEqual(0, d.Count);                    // item is gone
        Assert.AreEqual(0, d.Test.GenObjs.Count);
        Assert.AreEqual(0, d.SnapCount);                // snapshot is gone
        Assert.AreEqual(0, d.GenCount);                 // and generation has been dequeued
    }
    public void DontPanic()
    {
        var d = new SnapDictionary <int, string>();

        d.Test.CollectAuto = false;

        Assert.IsNull(d.Test.GenObj);

        // gen 1
        d.Set(1, "one");
        Assert.IsTrue(d.Test.NextGen);
        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsNull(d.Test.GenObj);

        var s1 = d.CreateSnapshot();

        Assert.IsFalse(d.Test.NextGen);
        Assert.AreEqual(1, d.Test.LiveGen);
        Assert.IsNotNull(d.Test.GenObj);
        Assert.AreEqual(1, d.Test.GenObj.Gen);

        Assert.AreEqual(1, s1.Gen);
        Assert.AreEqual("one", s1.Get(1));

        d.Set(1, "uno");
        Assert.IsTrue(d.Test.NextGen);
        Assert.AreEqual(2, d.Test.LiveGen);
        Assert.IsNotNull(d.Test.GenObj);
        Assert.AreEqual(1, d.Test.GenObj.Gen);

        var scopeContext  = new ScopeContext();
        var scopeProvider = GetScopeProvider(scopeContext);

        // scopeProvider.Context == scopeContext -> writer is scoped
        // writer is scope contextual and scoped
        //  when disposed, nothing happens
        //  when the context exists, the writer is released
        using (d.GetScopedWriteLock(scopeProvider))
        {
            d.SetLocked(1, "ein");
            Assert.IsTrue(d.Test.NextGen);
            Assert.AreEqual(3, d.Test.LiveGen);
            Assert.IsNotNull(d.Test.GenObj);
            Assert.AreEqual(2, d.Test.GenObj.Gen);
        }

        // writer has not released
        Assert.IsTrue(d.Test.IsLocked);
        Assert.IsNotNull(d.Test.GenObj);
        Assert.AreEqual(2, d.Test.GenObj.Gen);

        // nothing changed
        Assert.IsTrue(d.Test.NextGen);
        Assert.AreEqual(3, d.Test.LiveGen);

        // panic!
        var s2 = d.CreateSnapshot();

        Assert.IsTrue(d.Test.IsLocked);
        Assert.IsNotNull(d.Test.GenObj);
        Assert.AreEqual(2, d.Test.GenObj.Gen);
        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        // release writer
        scopeContext.ScopeExit(true);

        Assert.IsFalse(d.Test.IsLocked);
        Assert.IsNotNull(d.Test.GenObj);
        Assert.AreEqual(2, d.Test.GenObj.Gen);
        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsTrue(d.Test.NextGen);

        var s3 = d.CreateSnapshot();

        Assert.IsFalse(d.Test.IsLocked);
        Assert.IsNotNull(d.Test.GenObj);
        Assert.AreEqual(3, d.Test.GenObj.Gen);
        Assert.AreEqual(3, d.Test.LiveGen);
        Assert.IsFalse(d.Test.NextGen);
    }
    public void ScopeLocking2()
    {
        var d = new SnapDictionary <int, string>();
        var t = d.Test;

        t.CollectAuto = false;

        // gen 1
        d.Set(1, "one");
        var s1 = d.CreateSnapshot();

        Assert.AreEqual(1, s1.Gen);
        Assert.AreEqual("one", s1.Get(1));

        d.Set(1, "uno");
        var s2 = d.CreateSnapshot();

        Assert.AreEqual(2, s2.Gen);
        Assert.AreEqual("uno", s2.Get(1));

        Assert.AreEqual(2, t.LiveGen);
        Assert.IsFalse(t.NextGen);

        var scopeContext  = new ScopeContext();
        var scopeProvider = GetScopeProvider(scopeContext);

        using (d.GetScopedWriteLock(scopeProvider))
        {
            // creating a snapshot in a write-lock does NOT return the "current" content
            // it uses the previous snapshot, so new snapshot created only on release
            d.SetLocked(1, "ein");
            var s3 = d.CreateSnapshot();
            Assert.AreEqual(2, s3.Gen);
            Assert.AreEqual("uno", s3.Get(1));

            // we made some changes, so a next gen is required
            Assert.AreEqual(3, t.LiveGen);
            Assert.IsTrue(t.NextGen);
            Assert.IsTrue(t.IsLocked);

            // but live snapshot contains changes
            var ls = t.LiveSnapshot;
            Assert.AreEqual("ein", ls.Get(1));
            Assert.AreEqual(3, ls.Gen);
        }

        // nothing is committed until scope exits
        Assert.AreEqual(3, t.LiveGen);
        Assert.IsTrue(t.NextGen);
        Assert.IsTrue(t.IsLocked);

        // no changes until exit
        var s4 = d.CreateSnapshot();

        Assert.AreEqual(2, s4.Gen);
        Assert.AreEqual("uno", s4.Get(1));

        scopeContext.ScopeExit(false);

        // now things have changed
        Assert.AreEqual(2, t.LiveGen);
        Assert.IsFalse(t.NextGen);
        Assert.IsFalse(t.IsLocked);

        // no changes since not completed
        var s5 = d.CreateSnapshot();

        Assert.AreEqual(2, s5.Gen);
        Assert.AreEqual("uno", s5.Get(1));
    }
示例#7
0
 internal Snapshot(SnapDictionary <TKey, TValue> store, long gen)
 {
     _store = store;
     _gen   = gen;
 }
示例#8
0
 public TestHelper(SnapDictionary <TKey, TValue> dict) => _dict = dict;
示例#9
0
        public async Task CollectValues()
        {
            var d = new SnapDictionary <int, string>();

            d.Test.CollectAuto = false;

            // gen 1
            d.Set(1, "one");
            Assert.AreEqual(1, d.Test.GetValues(1).Length);
            d.Set(1, "one");
            Assert.AreEqual(1, d.Test.GetValues(1).Length);
            d.Set(1, "uno");
            Assert.AreEqual(1, d.Test.GetValues(1).Length);

            Assert.AreEqual(1, d.Test.LiveGen);
            Assert.IsTrue(d.Test.NextGen);

            SnapDictionary <int, string> .Snapshot s1 = d.CreateSnapshot();

            Assert.AreEqual(1, d.Test.LiveGen);
            Assert.IsFalse(d.Test.NextGen);

            // gen 2
            Assert.AreEqual(1, d.Test.GetValues(1).Length);
            d.Set(1, "one");
            Assert.AreEqual(2, d.Test.GetValues(1).Length);
            d.Set(1, "uno");
            Assert.AreEqual(2, d.Test.GetValues(1).Length);

            Assert.AreEqual(2, d.Test.LiveGen);
            Assert.IsTrue(d.Test.NextGen);

            SnapDictionary <int, string> .Snapshot s2 = d.CreateSnapshot();

            Assert.AreEqual(2, d.Test.LiveGen);
            Assert.IsFalse(d.Test.NextGen);

            // gen 3
            Assert.AreEqual(2, d.Test.GetValues(1).Length);
            d.Set(1, "one");
            Assert.AreEqual(3, d.Test.GetValues(1).Length);
            d.Set(1, "uno");
            Assert.AreEqual(3, d.Test.GetValues(1).Length);

            Assert.AreEqual(3, d.Test.LiveGen);
            Assert.IsTrue(d.Test.NextGen);

            SnapDictionary <int, string> .TestHelper.GenVal[] tv = d.Test.GetValues(1);
            Assert.AreEqual(3, tv[0].Gen);
            Assert.AreEqual(2, tv[1].Gen);
            Assert.AreEqual(1, tv[2].Gen);

            Assert.AreEqual(0, d.Test.FloorGen);

            // nothing to collect
            await d.CollectAsync();

            GC.KeepAlive(s1);
            GC.KeepAlive(s2);
            Assert.AreEqual(0, d.Test.FloorGen);
            Assert.AreEqual(3, d.Test.LiveGen);
            Assert.IsTrue(d.Test.NextGen);
            Assert.AreEqual(2, d.SnapCount);
            Assert.AreEqual(3, d.Test.GetValues(1).Length);

            // one snapshot to collect
            s1 = null;
            GC.Collect();
            GC.KeepAlive(s2);
            await d.CollectAsync();

            Assert.AreEqual(1, d.Test.FloorGen);
            Assert.AreEqual(3, d.Test.LiveGen);
            Assert.IsTrue(d.Test.NextGen);
            Assert.AreEqual(1, d.SnapCount);
            Assert.AreEqual(2, d.Test.GetValues(1).Length);

            // another snapshot to collect
            s2 = null;
            GC.Collect();
            await d.CollectAsync();

            Assert.AreEqual(2, d.Test.FloorGen);
            Assert.AreEqual(3, d.Test.LiveGen);
            Assert.IsTrue(d.Test.NextGen);
            Assert.AreEqual(0, d.SnapCount);
            Assert.AreEqual(1, d.Test.GetValues(1).Length);
        }