From ab8e0801232856cb5381d9a1bca840a2ad30d3d1 Mon Sep 17 00:00:00 2001 From: "Dr.Rx" Date: Tue, 12 Jun 2018 16:06:32 -0400 Subject: [PATCH 1/2] Add the Transactional.TryAddDistinct() --- .../TransactionalFixture.ImmutableList.cs | 90 ++++++++++++++++++- src/Uno.Core/Transactional.Immutables.cs | 71 +++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/src/Uno.Core.Tests/TransactionalFixture.ImmutableList.cs b/src/Uno.Core.Tests/TransactionalFixture.ImmutableList.cs index 27cc7a9..73ff19e 100644 --- a/src/Uno.Core.Tests/TransactionalFixture.ImmutableList.cs +++ b/src/Uno.Core.Tests/TransactionalFixture.ImmutableList.cs @@ -32,7 +32,7 @@ public void When_List_AddDistinct_Then_ItemAdded() var list = ImmutableList.Empty; var item = new object(); - Transactional.AddDistinct(ref list, item, EqualityComparer.Default); + Transactional.AddDistinct(ref list, item); Assert.AreEqual(1, list.Count); Assert.IsTrue(list.Contains(item)); @@ -44,6 +44,32 @@ public void When_List_AddDistinct_Twice_Then_ItemAddedOnlyOnce() var list = ImmutableList.Empty; var item = new object(); + var result1 = Transactional.AddDistinct(ref list, item); + var result2 = Transactional.AddDistinct(ref list, item); + + Assert.AreEqual(1, list.Count); + Assert.IsTrue(list.Contains(item)); + Assert.AreSame(result1, result2); + } + + [TestMethod] + public void When_List_AddDistinctWithComparer_Then_ItemAdded() + { + var list = ImmutableList.Empty; + var item = new object(); + + Transactional.AddDistinct(ref list, item, EqualityComparer.Default); + + Assert.AreEqual(1, list.Count); + Assert.IsTrue(list.Contains(item)); + } + + [TestMethod] + public void When_List_AddDistinctWithComparer_Twice_Then_ItemAddedOnlyOnce() + { + var list = ImmutableList.Empty; + var item = new object(); + var result1 = Transactional.AddDistinct(ref list, item, EqualityComparer.Default); var result2 = Transactional.AddDistinct(ref list, item, EqualityComparer.Default); @@ -51,5 +77,67 @@ public void When_List_AddDistinct_Twice_Then_ItemAddedOnlyOnce() Assert.IsTrue(list.Contains(item)); Assert.AreSame(result1, result2); } + + [TestMethod] + public void When_List_TryAddDistinct_Then_ItemAdded() + { + var list = ImmutableList.Empty; + var item = new object(); + + var added = Transactional.TryAddDistinct(ref list, item); + + Assert.IsTrue(added); + Assert.AreEqual(1, list.Count); + Assert.IsTrue(list.Contains(item)); + } + + [TestMethod] + public void When_List_TryAddDistinct_Twice_Then_ItemAddedOnlyOnce() + { + var list = ImmutableList.Empty; + var item = new object(); + + var added1 = Transactional.TryAddDistinct(ref list, item); + var result1 = list; + var added2 = Transactional.TryAddDistinct(ref list, item); + var result2 = list; + + Assert.IsTrue(added1); + Assert.IsFalse(added2); + Assert.AreEqual(1, list.Count); + Assert.IsTrue(list.Contains(item)); + Assert.AreSame(result1, result2); + } + + [TestMethod] + public void When_List_TryAddDistinctWithComparer_Then_ItemAdded() + { + var list = ImmutableList.Empty; + var item = new object(); + + var added = Transactional.TryAddDistinct(ref list, item, EqualityComparer.Default); + + Assert.IsTrue(added); + Assert.AreEqual(1, list.Count); + Assert.IsTrue(list.Contains(item)); + } + + [TestMethod] + public void When_List_TryAddDistinctWithCOmparer_Twice_Then_ItemAddedOnlyOnce() + { + var list = ImmutableList.Empty; + var item = new object(); + + var added1 = Transactional.TryAddDistinct(ref list, item, EqualityComparer.Default); + var result1 = list; + var added2 = Transactional.TryAddDistinct(ref list, item, EqualityComparer.Default); + var result2 = list; + + Assert.IsTrue(added1); + Assert.IsFalse(added2); + Assert.AreEqual(1, list.Count); + Assert.IsTrue(list.Contains(item)); + Assert.AreSame(result1, result2); + } } } diff --git a/src/Uno.Core/Transactional.Immutables.cs b/src/Uno.Core/Transactional.Immutables.cs index 3c481d4..2ace5e9 100644 --- a/src/Uno.Core/Transactional.Immutables.cs +++ b/src/Uno.Core/Transactional.Immutables.cs @@ -540,6 +540,29 @@ public static TList Add(ref TList list, T item) } } + /// + /// Transactionally add an item to a list if not already present. + /// + public static TList AddDistinct(ref TList list, T item) + where TList : class, IImmutableList + { + while (true) + { + var capture = list; + if (capture.IndexOf(item) >= 0) + { + return capture; + } + + var updated = (TList)capture.Add(item); + + if (Interlocked.CompareExchange(ref list, updated, capture) == capture) + { + return updated; + } + } + } + /// /// Transactionally add an item to a list if not already present. /// @@ -563,6 +586,54 @@ public static TList AddDistinct(ref TList list, T item, IEqualityCompa } } + /// + /// Transactionally try to add an item to a list if not already present. + /// + /// True if item was added, false if item was already present + public static bool TryAddDistinct(ref TList list, T item) + where TList : class, IImmutableList + { + while (true) + { + var capture = list; + if (capture.IndexOf(item) >= 0) + { + return false; + } + + var updated = (TList)capture.Add(item); + + if (Interlocked.CompareExchange(ref list, updated, capture) == capture) + { + return true; + } + } + } + + /// + /// Transactionally try to add an item to a list if not already present. + /// + /// True if item was added, false if item was already present + public static bool TryAddDistinct(ref TList list, T item, IEqualityComparer comparer) + where TList : class, IImmutableList + { + while (true) + { + var capture = list; + if (capture.IndexOf(item, comparer) >= 0) + { + return false; + } + + var updated = (TList)capture.Add(item); + + if (Interlocked.CompareExchange(ref list, updated, capture) == capture) + { + return true; + } + } + } + /// /// Transactionally remove an item from a list. /// From 95bec1ac1d0f0e90016ccab0d2560220dfb30ee2 Mon Sep 17 00:00:00 2001 From: "Dr.Rx" Date: Fri, 15 Jun 2018 10:49:43 -0400 Subject: [PATCH 2/2] Fix AsyncLock test concurrency issue --- src/Uno.Core.Tests/Threading/AsyncLockFixture.cs | 11 ++++------- src/Uno.Core.Tests/_TestUtils/AsyncTestRunner.cs | 9 +++++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Uno.Core.Tests/Threading/AsyncLockFixture.cs b/src/Uno.Core.Tests/Threading/AsyncLockFixture.cs index 7222fbb..5c97def 100644 --- a/src/Uno.Core.Tests/Threading/AsyncLockFixture.cs +++ b/src/Uno.Core.Tests/Threading/AsyncLockFixture.cs @@ -417,18 +417,15 @@ public async Task TestReleaseThenReAcquireWithConcurrentAccess() await otherThread.AdvanceTo(2); Assert.IsFalse(otherThread.HasLock()); - Task reLocking; using (await sut.LockAsync(CancellationToken.None)) { await Task.Yield(); - reLocking = otherThread.AdvanceTo(3); - await Task.Delay(10); - - Assert.AreEqual(TaskStatus.WaitingForActivation, reLocking.Status); + await otherThread.AdvanceAndFreezeBefore(3); + Assert.IsFalse(otherThread.HasLock()); } - await reLocking; + await otherThread.AdvanceTo(4); Assert.IsTrue(otherThread.HasLock()); } @@ -450,11 +447,11 @@ async Task OtherThread(CancellationToken ct, AsyncTestRunner r) await Task.Yield(); r.HasLock(true); r.Sync(position: 3); + r.Sync(position: 4); } await Task.Yield(); r.HasLock(false); - r.Sync(position: 4); } } diff --git a/src/Uno.Core.Tests/_TestUtils/AsyncTestRunner.cs b/src/Uno.Core.Tests/_TestUtils/AsyncTestRunner.cs index cd4b89d..767a907 100644 --- a/src/Uno.Core.Tests/_TestUtils/AsyncTestRunner.cs +++ b/src/Uno.Core.Tests/_TestUtils/AsyncTestRunner.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Reactive; using System.Runtime.CompilerServices; @@ -31,7 +32,7 @@ namespace Uno.Core.Tests.TestUtils public class AsyncTestRunner : IDisposable { #if DEBUG - private const int _beatTimeout = 10*1000; + private static int _beatTimeout => Debugger.IsAttached ? 10 * 1000 : 100; #else private const int _beatTimeout = 100; #endif @@ -199,7 +200,11 @@ public Task AdvanceTo(int position) { var flag = new SyncFlag(position); Interlocked.Exchange(ref _syncFlag, flag)?.Canceled(); - if (position <= _syncPosition) + if (position == _syncPosition) + { + return Task.CompletedTask; + } + else if (position < _syncPosition) { throw new InvalidOperationException("Thread is already at positon " + _syncPosition); }