public void TwoThreadsRemoveAt() { var mapper = new Mapper <int>(); const int Input = 21; var info = new CircularBucket <string>(16); var startEvent = new ManualResetEvent(false); int[] done = { 0 }; var threads = new[] { new Thread ( () => { int result; bool isNew; mapper.Set(0, Input, out isNew); info.Add("A: Set - was new: " + isNew); bool removed = mapper.RemoveAt(0, out result); info.Add(removed ? "A: Removed" : "A: Could not remove"); Interlocked.Increment(ref done[0]); } ), new Thread ( () => { int result; bool isNew; mapper.Set(0, Input, out isNew); info.Add("B: Set - was new: " + isNew); bool removed = mapper.RemoveAt(0, out result); info.Add(removed ? "B: Removed" : "B: Could not remove"); Interlocked.Increment(ref done[0]); } ) }; threads[0].Start(); threads[1].Start(); startEvent.Set(); while (true) { if (Thread.VolatileRead(ref done[0]) == 2) { break; } Thread.Sleep(0); } Assert.AreEqual(0, mapper.Count); }
public void TwoThreadsSet() { var context = new LockableContext(16); var needle = new LockableNeedle<int>(5, context); int[] count = {0}; var info = new CircularBucket<string>(64); Assert.AreEqual(5, needle.Value); Assert.Throws<InvalidOperationException>(() => needle.Value = 7); var threads = new [] { new Thread(() => { using (context.Enter()) { try { info.Add("First thread did enter."); var found = needle.Value; info.Add("First thread found: " + found + " will set: " + (found + 2)); needle.Value = found + 2; info.Add("First thread set: " + needle.Value); info.Add("First thread set count to: " + Interlocked.Increment(ref count[0])); info.Add("First thread done."); } catch (Exception exc) { info.Add("First thread exception: " + exc.Message); throw; } } info.Add("First thread left."); }), new Thread(() => { using (context.Enter()) { try { info.Add("Second thread did enter."); var found = needle.Value; info.Add("Second thread found: " + found + " will set: " + (found + 3)); needle.Value = found + 3; info.Add("Second thread set: " + needle.Value); info.Add("Second thread set count to: " + Interlocked.Increment(ref count[0])); info.Add("Second thread done."); } catch (Exception exc) { info.Add("Second thread exception: " + exc.Message); throw; } } info.Add("Second thread left."); }) }; threads[0].Start(); threads[1].Start(); threads[0].Join(); threads[1].Join(); foreach (var item in info) { Trace.WriteLine(item); } Trace.WriteLine("Count = " + Thread.VolatileRead(ref count[0])); Trace.WriteLine("Found = " + needle.Value); Assert.IsTrue(needle.Value == 7 || needle.Value == 8 || needle.Value == 10); }
public void TwoThreadsSet() { var context = new LockableContext(16); var needle = new LockableNeedle <int>(5, context); int[] count = { 0 }; var info = new CircularBucket <string>(64); Assert.AreEqual(5, needle.Value); Assert.Throws <InvalidOperationException>(() => needle.Value = 7); var threads = new[] { new Thread(() => { using (context.Enter()) { try { info.Add("First thread did enter."); var found = needle.Value; info.Add("First thread found: " + found.ToString() + " will set: " + (found + 2).ToString()); needle.Value = found + 2; info.Add("First thread set: " + needle.Value.ToString()); info.Add("First thread set count to: " + Interlocked.Increment(ref count[0]).ToString()); info.Add("First thread done."); } catch (Exception exc) { info.Add("First thread exception: " + exc.Message); throw; } } info.Add("First thread left."); }), new Thread(() => { using (context.Enter()) { try { info.Add("Second thread did enter."); var found = needle.Value; info.Add("Second thread found: " + found.ToString() + " will set: " + (found + 3).ToString()); needle.Value = found + 3; info.Add("Second thread set: " + needle.Value.ToString()); info.Add("Second thread set count to: " + Interlocked.Increment(ref count[0]).ToString()); info.Add("Second thread done."); } catch (Exception exc) { info.Add("Second thread exception: " + exc.Message); throw; } } info.Add("Second thread left."); }) }; threads[0].Start(); threads[1].Start(); threads[0].Join(); threads[1].Join(); foreach (var item in info) { Trace.WriteLine(item); } Trace.WriteLine("Count = " + Volatile.Read(ref count[0]).ToString()); Trace.WriteLine("Found = " + needle.Value.ToString()); Assert.IsTrue(needle.Value == 7 || needle.Value == 8 || needle.Value == 10); }
public void TransactionalDataStructure() { var info = new CircularBucket <string>(32); var bucket = new NeedleBucket <int, Transact.Needle <int> >(index => index, value => new Transact.Needle <int>(value), 5); var didA = false; bool didB; using (var enteredWorkA = new ManualResetEvent(false)) { using (var enteredWorkB = new ManualResetEvent(false)) { using (var done = new ManualResetEvent(false)) { ManualResetEvent[] handles = { enteredWorkA, enteredWorkB, done }; ThreadPool.QueueUserWorkItem ( _ => { info.Add("Work A - start"); using (var transact = new Transact()) { info.Add("Work A - enter"); handles[0].Set(); info.Add("Work A - reported, waiting"); handles[1].WaitOne(); info.Add("Work A - going"); // foreach will not trigger the creation of items var got = new int[5]; var set = new int[5]; for (var index = 0; index < 5; index++) { got[index] = bucket.GetNeedle(index).Value; set[index] = got[index] + 1; bucket.GetNeedle(index).Value = set[index]; } info.Add(string.Format("Work A - Got: [{0}, {1}, {2}, {3}, {4}] - Set: [{5}, {6}, {7}, {8}, {9}]", got[0], got[1], got[2], got[3], got[4], set[0], set[1], set[2], set[3], set[4])); if (!bucket.SequenceEqual(set)) { info.Add("Work A - ??"); } info.Add("Work A - before commit"); didA = transact.Commit(); info.Add("Work A - after commit: " + didA); if (didA != bucket.SequenceEqual(set)) { info.Add("Work A - ???"); } info.Add("Work A - report"); handles[2].Set(); info.Add("Work A - done"); } } ); { info.Add("Work B - start"); using (var transact = new Transact()) { info.Add("Work B - waiting A to enter"); handles[0].WaitOne(); info.Add("Work B - telling Work A to go"); handles[1].Set(); info.Add("Work B - going"); // foreach will not trigger the creation of items var got = new int[5]; var set = new int[5]; for (var index = 0; index < 5; index++) { got[index] = bucket.GetNeedle(index).Value; set[index] = got[index] * 2; bucket.GetNeedle(index).Value = set[index]; } info.Add(string.Format("Work B - Got: [{0}, {1}, {2}, {3}, {4}] - Set: [{5}, {6}, {7}, {8}, {9}]", got[0], got[1], got[2], got[3], got[4], set[0], set[1], set[2], set[3], set[4])); if (!bucket.SequenceEqual(set)) { info.Add("Work B - ??"); } info.Add("Work B - before commit"); didB = transact.Commit(); info.Add("Work B - after commit: " + didB); if (didB != bucket.SequenceEqual(set)) { info.Add("Work B - ???"); } info.Add("Work B - waiting report"); handles[2].WaitOne(); info.Add("Work B - done"); } } var result = bucket; // These are more likely in debug mode // (+1) if (result.SequenceEqual(new[] { 1, 2, 3, 4, 5 })) { Assert.IsTrue(didA); Assert.IsFalse(didB); return; } // (*2) if (result.SequenceEqual(new[] { 0, 2, 4, 6, 8 })) { Assert.IsFalse(didA); Assert.IsTrue(didB); return; } // This are more likely with optimization enabled // (+1) and then (*2) if (result.SequenceEqual(new[] { 2, 4, 6, 8, 10 })) { Assert.IsTrue(didA); Assert.IsTrue(didB); return; } // (*2) and then (+1) if (result.SequenceEqual(new[] { 1, 3, 5, 7, 9 })) { Assert.IsTrue(didA); Assert.IsTrue(didB); return; } //--- if (result.SequenceEqual(new[] { 0, 1, 2, 3, 4 })) { Assert.IsFalse(didA); Assert.IsFalse(didB); return; } var found = result.ToArray(); Trace.WriteLine(" --- REPORT --- "); foreach (var msg in info) { Trace.WriteLine(msg); } Assert.Fail("T_T - This is what was found: [{0}, {1}, {2}, {3}, {4}]", found[0], found[1], found[2], found[3], found[4]); } } } }
private static bool WaitAsyncWaitCorrectlyExtractedExtracted(int maxCount, int maxTasks) { // Note: if WaitAsync takes to long, "x" can happen before the chunk of "a" has completed. var log = new CircularBucket <string>((maxTasks * 4) + 2); var logCount = new CircularBucket <int>((maxTasks * 2) + 2); var source = new CancellationTokenSource[1]; using (source[0] = new CancellationTokenSource()) { source[0].CancelAfter(TimeSpan.FromSeconds(100)); var semaphore = new SemaphoreSlim[1]; using (semaphore[0] = new SemaphoreSlim(0, maxCount)) { // No task should be able to enter semaphore at this point. // Thus semaphore.CurrentCount should be 0 // We can directly check Assert.AreEqual(0, semaphore[0].CurrentCount); var padding = 0; var tasks = Enumerable.Range(0, maxTasks) .Select ( _ => { return(Task.Factory.StartNew ( async() => { log.Add("a"); await semaphore[0].WaitAsync ( source[0].Token ).ConfigureAwait(false); Interlocked.Add(ref padding, 100); logCount.Add(-1); log.Add("b"); Thread.Sleep(1000 + padding); // Calling release should give increasing results per chunk log.Add("c"); var count = semaphore[0].Release(); logCount.Add(count); log.Add("d"); } ).Unwrap()); } ).ToArray(); Thread.Sleep(TimeSpan.FromMilliseconds(500)); log.Add("x"); var tmp = semaphore[0].Release(maxCount); logCount.Add(-1); logCount.Add(tmp); Task.WaitAll(tasks, source[0].Token); log.Add("z"); } } // We should see: // maxTask a // 1 x // chunks of at most maxCount b, separated by chunks of c var sb = new StringBuilder(log.Capacity); foreach (var entry in log) { sb.Append(entry); } var str = sb.ToString(); Console.WriteLine(str); // Make sure that threads have not sneaked in the ordering // If this has happen, it would have been a false failure // So, we will retry until it does not happen if (new Regex("c[bc]+d").IsMatch(str)) { Console.WriteLine("..."); return(false); } var regexSuccess = $"a{{{maxTasks}}}x(b{{0,{maxCount}}}(cd)+)+z"; Assert.IsTrue(new Regex(regexSuccess).IsMatch(str)); // The results of release increase *per chunk of c*. var last = -1; var first = true; foreach (var entry in logCount) { Console.WriteLine(entry.ToString()); if (entry == -1) { first = true; continue; } if (first) { first = false; } else if (entry < last) { Assert.Fail(); } last = entry; } return(true); }
private static CircularBucket <int> WaitAsyncWaitCorrectlyExtractedCore(int maxCount, int maxTasks, out string str) { var log = new CircularBucket <string>((maxTasks * 4) + 2); var logCount = new CircularBucket <int>((maxTasks * 2) + 2); var semaphores = new SemaphoreSlim[1]; var sources = new CancellationTokenSource[1]; using (sources[0] = new CancellationTokenSource(TimeSpan.FromSeconds(100))) { using (semaphores[0] = new SemaphoreSlim(0, maxCount)) { // No task should be able to enter semaphore at this point. // Thus semaphore.CurrentCount should be 0 // We can directly check Assert.AreEqual(0, semaphores[0].CurrentCount); var padding = 0; var tasks = Enumerable.Range(0, maxTasks).Select ( _ => Task.Factory.StartNew ( async() => { log.Add("a"); await semaphores[0].WaitAsync(sources[0].Token); Interlocked.Add(ref padding, 100); logCount.Add(-1); log.Add("b"); Thread.Sleep(1000 + padding); // Calling release should give increasing results per chunk log.Add("c"); var count = semaphores[0].Release(); logCount.Add(count); log.Add("d"); } ).Unwrap() ).ToArray(); Thread.Sleep(TimeSpan.FromMilliseconds(500)); log.Add("x"); var tmp = semaphores[0].Release(maxCount); logCount.Add(-1); logCount.Add(tmp); Task.WaitAll(tasks, sources[0].Token); log.Add("z"); } } // We should see: // maxTask a // 1 x // chunks of at most maxCount b, separated by chunks of c var sb = new StringBuilder(log.Capacity); foreach (var entry in log) { sb.Append(entry); } str = sb.ToString(); return(logCount); }
public void EditWhileIteratingThreaded() { var d = new ConcurrentDictionary <string, string>(); Assert.IsTrue(d.TryAdd("0", "1")); Assert.IsTrue(d.TryAdd("a", "b")); int[] expectedCount = { 2 }; Assert.AreEqual(expectedCount[0], d.Count); string a = null; var foundCount = 0; var didAdd = 0; var didRemove = 0; var found = new CircularBucket <string>(16); ThreadStart remover = () => { var removed = d.TryRemove("a", out a); if (Thread.VolatileRead(ref didRemove) == 0 && removed) { expectedCount[0]--; } if (removed) { Interlocked.CompareExchange(ref didRemove, 1, 0); } }; ThreadStart adder = () => { var added = d.TryAdd("c", "d"); if (Thread.VolatileRead(ref didAdd) == 0 && added) { expectedCount[0]++; } if (added) { Interlocked.CompareExchange(ref didAdd, 1, 0); } }; // MSDN says: "it does not represent a moment-in-time snapshot of the dictionary." // And also "The contents exposed through the enumerator may contain modifications made to the dictionary after GetEnumerator was called." foreach (var item in d) { found.Add(item.Key); foundCount++; var old = expectedCount[0]; Assert.AreEqual(expectedCount[0], d.Count); { var t = new Thread(remover); t.Start(); t.Join(); } if (foundCount == 1) { Assert.AreNotEqual(old, expectedCount[0]); } else { Assert.AreEqual(old, expectedCount[0]); } Assert.AreEqual(expectedCount[0], d.Count); old = expectedCount[0]; { var t = new Thread(adder); t.Start(); t.Join(); } if (foundCount == 1) { Assert.AreNotEqual(old, expectedCount[0]); } else { Assert.AreEqual(old, expectedCount[0]); } Assert.AreEqual(expectedCount[0], d.Count); } Assert.IsNull(a); var array = found.ToArray(); if (!array.IsSupersetOf(new[] { "0", "c" })) { foreach (var item in array) { Console.WriteLine(item); } Assert.Fail(); } Assert.AreEqual(2, expectedCount[0]); Assert.AreEqual(1, didAdd); Assert.AreEqual(1, didRemove); Assert.IsTrue(foundCount - expectedCount[0] < 2, "foundCount: {0}, expectedCount:{1}", foundCount, expectedCount[0]); Assert.AreEqual(expectedCount[0], d.Count); }
public void EditWhileIterating() { var d = new ConcurrentDictionary <string, string>(); Assert.IsTrue(d.TryAdd("0", "1")); Assert.IsTrue(d.TryAdd("a", "b")); var expectedCount = 2; Assert.AreEqual(expectedCount, d.Count); string a = null; var foundCount = 0; var didAdd = false; var didRemove = false; var found = new CircularBucket <string>(16); // MSDN says: "it does not represent a moment-in-time snapshot of the dictionary." // And also "The contents exposed through the enumerator may contain modifications made to the dictionary after GetEnumerator was called." // Note: There is no guarantee the items are in insert order foreach (var item in d) { found.Add(item.Key); foundCount++; Assert.AreEqual(expectedCount, d.Count); var removed = d.TryRemove("a", out a); if (didRemove) { Assert.IsFalse(removed); } else { Assert.IsTrue(removed); expectedCount--; } didRemove = didRemove | removed; Assert.IsTrue(didRemove); Assert.AreEqual(expectedCount, d.Count); var added = d.TryAdd("c", "d"); if (didAdd) { Assert.IsFalse(added); } else { Assert.IsTrue(added); expectedCount++; } didAdd = didAdd | added; Assert.IsTrue(didAdd); Assert.AreEqual(expectedCount, d.Count); } Assert.IsNull(a); var array = found.ToArray(); if (!array.IsSupersetOf(new[] { "0", "c" })) { foreach (var item in array) { Console.WriteLine(item); } Assert.Fail(); } Assert.AreEqual(2, expectedCount); Assert.AreEqual(true, didAdd); Assert.AreEqual(true, didRemove); Assert.IsTrue(foundCount - expectedCount < 2, "foundCount: {0}, expectedCount:{1}", foundCount, expectedCount); Assert.AreEqual(expectedCount, d.Count); }
public void TwoThreadsRemoveAt() { var mapper = new Mapper<int>(); const int Input = 21; var info = new CircularBucket<string>(16); var startEvent = new ManualResetEvent(false); int[] done = {0}; var threads = new[] { new Thread ( () => { int result; bool isNew; mapper.Set(0, Input, out isNew); info.Add("A: Set - was new: " + isNew); bool removed = mapper.RemoveAt(0, out result); info.Add(removed ? "A: Removed" : "A: Could not remove"); Interlocked.Increment(ref done[0]); } ), new Thread ( () => { int result; bool isNew; mapper.Set(0, Input, out isNew); info.Add("B: Set - was new: " + isNew); bool removed = mapper.RemoveAt(0, out result); info.Add(removed ? "B: Removed" : "B: Could not remove"); Interlocked.Increment(ref done[0]); } ) }; threads[0].Start(); threads[1].Start(); startEvent.Set(); while (true) { if (Thread.VolatileRead(ref done[0]) == 2) { break; } Thread.Sleep(0); } Assert.AreEqual(0, mapper.Count); }