/// <summary>Copies a specified number of items into an array.</summary> public void CopyTo(int sourceIndex, T[] destination, int destinationIndex, int count) { Utility.ValidateRange(Count, sourceIndex, count); Utility.ValidateRange(destination, destinationIndex, count); STM.Retry(delegate { for (int i = 0; i < count; i++) { destination[destinationIndex + i] = array[sourceIndex + i].Read(); } }); }
/// <summary>Returns the index of the first item in the given region of the array equal to the given item, or -1 if no such /// item could be found. /// </summary> public int IndexOf(T item, int index, int count) { Utility.ValidateRange(Count, index, count); return(STM.Retry(delegate { for (int end = index + count; index < end; index++) { if (comparer.Equals(item, array[index].Read())) { return index; } } return -1; })); }
/// <summary>Determines whether the given item exists in a given range within the array.</summary> public bool Contains(T item, int index, int count) { Utility.ValidateRange(Count, index, count); return(STM.Retry(delegate { for (int i = index, end = index + count; i < end; i++) { if (comparer.Equals(item, array[i].Read())) // if an item is found, changes to previous items won't affect the result, { // so we can release them to avoid false conflicts while (i > index) { array[--i].Release(); } return true; } } return false; })); }
public void T05_TestContention() { // test the EnsureConsistency option TransactionalVariable <int> a = STM.Allocate <int>(), b = STM.Allocate <int>(); using (STMTransaction tx = STMTransaction.Create(STMOptions.EnsureConsistency)) { Assert.IsTrue(tx.EnsureConsistency); a.Read(); Assert.IsTrue(a.IsConsistent()); Assert.IsTrue(tx.IsConsistent()); a.CheckConsistency(); TestHelpers.RunInAnotherThread(delegate { STM.Retry(delegate { a.Set(1); }); }); Assert.IsFalse(a.IsConsistent()); Assert.IsFalse(tx.IsConsistent()); TestHelpers.TestException <TransactionAbortedException>(delegate { a.CheckConsistency(); }); TestHelpers.TestException <TransactionAbortedException>(delegate { tx.CheckConsistency(); }); TestHelpers.TestException <TransactionAbortedException>(delegate { b.Read(); }); } const int Iterations = 500; TransactionalVariable <int>[] vars = new TransactionalVariable <int> [10]; // the array length must be even for (int i = 0; i < vars.Length; i++) { vars[i] = STM.Allocate <int>(); } Random rand = new Random(); Exception exception = null; ThreadStart code = delegate { try { int index; lock (rand) index = rand.Next(vars.Length); // start at a random location in each thread for (int time = 0; time < Iterations; time++) { // create a loop that increments every variable in the array once in a series of small transactions that write // two elements and read the intervening ones for (int i = 0; i < vars.Length / 2; i++) { int origIndex = index; // store the index so we can restore it if a transaction aborts STM.Retry(delegate { index = origIndex; vars[index].Set(vars[index].OpenForWrite() + 1); if (++index == vars.Length) { index = 0; } for (int j = 0; j < vars.Length / 2 - 1; j++) { vars[index].Read(); if (++index == vars.Length) { index = 0; } } vars[index].Set(vars[index].OpenForWrite() + 1); if (++index == vars.Length) { index = 0; } }); origIndex = index; } } } catch (Exception ex) { exception = ex; } }; Thread[] threads = new Thread[16]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(code); } for (int i = 0; i < threads.Length; i++) { threads[i].Start(); } for (int i = 0; i < threads.Length; i++) { if (!threads[i].Join(10000)) { threads[i].Abort(); // give the test 10 seconds to run } } if (exception != null) { throw exception; } // make sure all variables were incremented the right number of times for (int i = 0; i < vars.Length; i++) { Assert.AreEqual(Iterations * threads.Length, vars[i].ReadWithoutOpening()); } }
public void T03_TestErrors() { // test that variables can't be created with uncloneable types TestHelpers.TestException <NotSupportedException>(delegate { STM.Allocate <object>(); }); TestHelpers.TestException <NotSupportedException>(delegate { STM.Allocate <UncopyableStruct>(); }); // test that transactions can't be committed twice using (STMTransaction tx = STMTransaction.Create()) { tx.Commit(); TestHelpers.TestException <InvalidOperationException>(delegate { tx.Commit(); }); } // test that variables can't be opened for write outside transactions TransactionalVariable <int> a = STM.Allocate <int>(); TestHelpers.TestException <InvalidOperationException>(delegate { a.OpenForWrite(); }); TestHelpers.TestException <InvalidOperationException>(delegate { a.Set(1); }); // test that bad implementations of ICloneable are detected TransactionalVariable <BadCloneable> b = STM.Allocate(new BadCloneable()); using (STMTransaction tx = STMTransaction.Create()) { TestHelpers.TestException <InvalidOperationException>(delegate { b.OpenForWrite(); }); } // test that a transaction can't be committed before nested transactions have been dealt with using (STMTransaction otx = STMTransaction.Create()) { STMTransaction tx = STMTransaction.Create(); TestHelpers.TestException <InvalidOperationException>(delegate { otx.Commit(); }); } // test that STM.Retry() fails when a transaction times out TestHelpers.TestException <TransactionAbortedException>(delegate { STM.Retry(25, delegate { a.Read(); TestHelpers.RunInAnotherThread(delegate { STM.Retry(delegate { a.Set(a.OpenForWrite() + 1); }); }); }); }); // test that STM.Retry() doesn't loop infinitely if a transaction was consistent when an exception was thrown TransactionalVariable <int> c = STM.Allocate(0); TestHelpers.TestException <ArgumentOutOfRangeException>(delegate { STM.Retry(delegate { a.Read(); c.Set(42); throw new ArgumentOutOfRangeException(); }); }); AssertEqual(c, 0); // but test that STM.Retry() does retry if the transaction was inconsistent STM.Retry(delegate { a.Set(0); }); bool first = true; STM.Retry(delegate { int aValue = a.Read(); c.Set(42); if (first) { first = false; TestHelpers.RunInAnotherThread(delegate { STM.Retry(delegate { a.Set(a.OpenForWrite() + 1); }); }); } if (aValue == 0) { throw new ArgumentOutOfRangeException(); } }); AssertEqual(c, 42); }
public void T01_TestBasicTransactions() { TransactionalVariable <int> a = STM.Allocate(1), b = STM.Allocate(2), c = STM.Allocate(3); AssertEqual(a, 1, b, 2, c, 3); // test a simple transaction that doesn't commit using (STMTransaction tx = STMTransaction.Create()) { a.Read(); // open one for reading b.OpenForWrite(); // one for writing AssertEqual(a, 1, b, 2, c, 3); // and leave one unopened, and check their values c.Set(30); a.Set(10); AssertEqual(a, 10, b, 2, c, 30); } AssertEqual(a, 1, b, 2, c, 3); // check that the changes were reverted // test a transaction that does commit using (STMTransaction tx = STMTransaction.Create()) { AssertEqual(a, 1, b, 2, c, 3); // check that the changes were reverted c.Set(30); a.Set(10); b.Set(20); tx.Commit(); AssertEqual(a, 10, b, 20, c, 30); } AssertEqual(a, 10, b, 20, c, 30); // test a simple nested transaction where the inner doesn't commit using (STMTransaction otx = STMTransaction.Create()) { a.Set(-1); c.Read(); AssertEqual(a, -1, c, 30); using (STMTransaction tx = STMTransaction.Create()) { AssertEqual(a, -1); a.Set(1); b.Set(2); c.Set(3); AssertEqual(a, 1, b, 2, c, 3); } AssertEqual(a, -1, b, 20, c, 30); otx.Commit(); } AssertEqual(a, -1, b, 20, c, 30); // test a simple nested transaction where the outer doesn't commit but the inner does using (STMTransaction otx = STMTransaction.Create()) { a.Set(1); c.Read(); AssertEqual(a, 1, c, 30); using (STMTransaction tx = STMTransaction.Create()) { AssertEqual(a, 1); a.Set(10); b.Set(-2); c.Set(-3); AssertEqual(a, 10, b, -2, c, -3); tx.Commit(); } AssertEqual(a, 10, b, -2, c, -3); } AssertEqual(a, -1, b, 20, c, 30); // test a simple nested transaction where both commit using (STMTransaction otx = STMTransaction.Create()) { a.Set(1); b.Read(); b.Set(-2); c.Read(); AssertEqual(a, 1, b, -2, c, 30); using (STMTransaction tx = STMTransaction.Create()) { a.Set(-1); b.Set(2); c.Set(3); tx.Commit(); } AssertEqual(a, -1, b, 2, c, 3); a.Set(1); otx.Commit(); } AssertEqual(a, 1, b, 2, c, 3); // test the Release() method using (STMTransaction tx = STMTransaction.Create()) { a.Set(10); // test that releasing a written variable discards changes AssertEqual(a, 10); b.Read(); // test that releasing a read variable prevents conflicts with other transactions c.Read(); TestHelpers.RunInAnotherThread(delegate { STM.Retry(delegate { b.Set(20); c.Set(30); }); }); AssertEqual(b, 2); a.Release(); b.Release(); c.Release(); b.Read(); AssertEqual(a, 1, b, 20); tx.Commit(); } AssertEqual(a, 1, b, 20, c, 30); // test post-commit actions int ntValue = 0; using (STMTransaction tx = STMTransaction.Create()) { a.Set(10); tx.Commit(delegate { ntValue = 1; }); } Assert.AreEqual(1, ntValue); // ensure they're not executed unless the top-level transaction commits using (STMTransaction otx = STMTransaction.Create()) using (STMTransaction tx = STMTransaction.Create()) { a.Set(10); tx.Commit(delegate { ntValue = 2; }); } Assert.AreEqual(1, ntValue); // ensure that they're executed in the right order (innermost first) and at the right time using (STMTransaction otx = STMTransaction.Create()) { using (STMTransaction tx = STMTransaction.Create()) { a.Set(10); tx.Commit(delegate { ntValue = 2; }); Assert.AreEqual(1, ntValue); } Assert.AreEqual(1, ntValue); otx.Commit(delegate { ntValue++; }); } Assert.AreEqual(3, ntValue); // ensure that post-commit actions given to STM.Retry() work and aren't executed multiple times if the transaction restarts bool first = true; STM.Retry( delegate { if (first) { first = false; throw new TransactionAbortedException(); } }, delegate { ntValue++; }); Assert.AreEqual(4, ntValue); }
/// <inheritdoc/> public T this[int index] { get { return(array[index].Read()); } set { STM.Retry(delegate { array[index].Set(value); }); } }
public void T01_TestArray() { // test the constructor that takes a count Assert.AreEqual(2, new TransactionalArray <int>(2).Count); // test the constructor that takes a list of items TransactionalArray <int> array = new TransactionalArray <int>(new int[] { 0, 1, 2, 3, 4 }); Assert.AreEqual(5, array.Count); for (int i = 0; i < 5; i++) { Assert.AreEqual(i, array[i]); } // ensure that the array can be changed outside a transaction array[0] = 5; Assert.AreEqual(5, array[0]); array[0] = 0; using (STMTransaction tx = STMTransaction.Create()) { // test indexing array[2] = 42; Assert.AreEqual(42, array[2]); // test IndexOf() and Contains() Assert.AreEqual(2, array.IndexOf(42)); Assert.IsTrue(array.Contains(42)); Assert.AreEqual(-1, array.IndexOf(55)); Assert.IsFalse(array.Contains(55)); // test CopyTo() int[] extArray = new int[5]; array.CopyTo(extArray, 0); for (int i = 0; i < 5; i++) { Assert.AreEqual(i == 2 ? 42 : i, extArray[i]); } // test the enumerator List <int> list = new List <int>(); foreach (int i in array) { list.Add(i); } Assert.AreEqual(5, list.Count); for (int i = 0; i < 5; i++) { Assert.AreEqual(i == 2 ? 42 : i, list[i]); } // test isolation TestHelpers.RunInAnotherThread(delegate { Assert.AreEqual(2, array[2]); }); tx.Commit(); } Assert.AreEqual(42, array[2]); // test that operations compose transactionally using (STMTransaction otx = STMTransaction.Create()) { STM.Retry(delegate { array[2] = 2; }); STM.Retry(delegate { array[0] = -1; }); Assert.AreEqual(-1, array[0]); Assert.AreEqual(2, array[2]); TestHelpers.RunInAnotherThread(delegate { Assert.AreEqual(0, array[0]); Assert.AreEqual(42, array[2]); }); otx.Commit(); } Assert.AreEqual(-1, array[0]); Assert.AreEqual(2, array[2]); // test array enlarging array.Enlarge(0); // test that the array can't be shrunk Assert.AreEqual(5, array.Count); array.Enlarge(6); // test that it can be enlarged Assert.AreEqual(6, array.Count); Assert.AreEqual(-1, array[0]); // test that enlarging it doesn't change any values Assert.AreEqual(2, array[2]); }