public void DegeneratingCommuteTest() { var a = new Shielded <int>(); int numInc = 100000; int transactionCount = 0, commuteCount = 0; ParallelEnumerable.Repeat(1, numInc / 2).ForAll(i => Shield.InTransaction(() => { Interlocked.Increment(ref transactionCount); a.Commute((ref int n) => { Interlocked.Increment(ref commuteCount); n++; }); a.Commute((ref int n) => { Interlocked.Increment(ref commuteCount); n++; }); // so, we cause it to degenerate. there was a subtle bug in enlisting which // would allow a degenerated commute to execute before checking the lock! int x = a; })); Assert.AreEqual(numInc, a); // degenerated commutes conflict, which means transaction will repeat. conflict // may be detected before the commute lambda actually gets to execute, so the // trans count can be greater than commute count. Assert.GreaterOrEqual(transactionCount, commuteCount / 2); if (commuteCount == numInc) { Assert.Inconclusive(); } }
public void SideEffectsInCommutes() { // due to running in an isolated context, side-effects from commutes get // separately tracked, but must eventually execute just like any other // SideEffect. this basically tests a part of TransactionItems.UnionWith(). var x = new Shielded <int>(); bool didItRun = false; Thread t = null; Shield.InTransaction(() => x.Commute((ref int _) => { OneTimeConflict(ref t, x); Shield.SideEffect(() => { Assert.IsFalse(didItRun); didItRun = true; }); })); Assert.IsTrue(didItRun); didItRun = false; t = null; Shield.InTransaction(() => x.Commute((ref int _) => { OneTimeConflict(ref t, x); Shield.SyncSideEffect(() => { Assert.IsFalse(didItRun); didItRun = true; }); })); Assert.IsTrue(didItRun); }
public void CommuteInACommute() { var a = new Shielded <int>(); var b = new Shielded <int>(); Assert.Throws <InvalidOperationException>(() => // there is only one _blockEnlist, and if it is "reused" by an inner call, it // would get set to null when the inner commute ends. this would allow later code // in the outer commute to violate the access restriction. Shield.InTransaction(() => a.Commute((ref int aRef) => { a.Commute((ref int aRef2) => aRef2++); b.Value = 1; }))); }
public void CommuteInACommute() { var a = new Shielded<int>(); var b = new Shielded<int>(); Assert.Throws<InvalidOperationException>(() => // there is only one _blockEnlist, and if it is "reused" by an inner call, it // would get set to null when the inner commute ends. this would allow later code // in the outer commute to violate the access restriction. Shield.InTransaction(() => a.Commute((ref int aRef) => { a.Commute((ref int aRef2) => aRef2++); b.Value = 1; }))); }
public void SyncSideEffectReadsCommute() { // in a SyncSideEffect you can read from a commuted field, but your ReadStamp has been set // back to its original value, which is possibly lower than that field's last written // value from some other, concurrent transaction. fields used to check for this, and roll back // the transaction after it was already checked! var a = new Shielded <int>(); int lastReadValue = 0; Shield.InTransaction(() => { a.Commute((ref int x) => x++); var otherThread = new Thread(() => Shield.InTransaction(() => { a.Value = 10; })); otherThread.Start(); otherThread.Join(); Shield.SideEffect(null, () => Assert.Fail("Invalid rollback occurred.")); Shield.SyncSideEffect(() => { // here, the field's last written value has version 1, and our ReadStamp is 0. // however, the commute subtransaction had ReadStamp 1, and it checked out, so, // this should not be a problem. lastReadValue = a.Value; }); }); Assert.AreEqual(11, lastReadValue); }
public void EventTest() { var a = new Shielded <int>(1); var eventCount = new Shielded <int>(); EventHandler <EventArgs> ev = (sender, arg) => eventCount.Commute((ref int e) => e++); Assert.Throws <InvalidOperationException>(() => a.Changed.Subscribe(ev)); Shield.InTransaction(() => { a.Changed.Subscribe(ev); var t = new Thread(() => Shield.InTransaction(() => a.Modify((ref int x) => x++))); t.Start(); t.Join(); var t2 = new Thread(() => Shield.InTransaction(() => a.Modify((ref int x) => x++))); t2.Start(); t2.Join(); }); Assert.AreEqual(0, eventCount); Shield.InTransaction(() => { a.Modify((ref int x) => x++); }); Assert.AreEqual(1, eventCount); Thread tUnsub = null; Shield.InTransaction(() => { a.Changed.Unsubscribe(ev); a.Modify((ref int x) => x++); if (tUnsub == null) { tUnsub = new Thread(() => { Shield.InTransaction(() => { a.Modify((ref int x) => x++); a.Modify((ref int x) => x++); }); }); tUnsub.Start(); tUnsub.Join(); } }); // the other thread must still see the subscription... Assert.AreEqual(3, eventCount); Shield.InTransaction(() => a.Modify((ref int x) => x++)); Assert.AreEqual(3, eventCount); }
public void PreCommitFirstCommute() { var a = new Shielded <int>(); var b = new Shielded <int>(); using (Shield.PreCommit(() => a == 0 || true, () => b.Commute((ref int n) => n++))) Shield.InTransaction(() => a.Value = 1); Assert.AreEqual(1, a); Assert.AreEqual(1, b); }
public void CommuteInvariantProblem() { // since pre-commits have no limitations on access, they cannot safely // execute within the commute sub-transaction. if they would, then this // test would fail on the last assertion. instead, pre-commits cause commutes // to degenerate. var testField = new Shielded <int>(); var effectField = new Shielded <int>(); var failCommitCount = new Shielded <int>(); var failVisibleCount = 0; // check if the effect field was written to, that the testField is even. Shield.PreCommit(() => effectField > 0, () => { if ((testField & 1) == 1) { Interlocked.Increment(ref failVisibleCount); // this will always fail to commit, confirming that the transaction // is already bound to fail. but, the failVisibleCount might be >0. failCommitCount.Modify((ref int n) => n++); } }); var thread = new Thread(() => { // if the testField is even, increment the effectField commutatively. foreach (int i in Enumerable.Range(1, 1000)) { Shield.InTransaction(() => { if ((testField & 1) == 0) { effectField.Commute((ref int n) => n++); } }); } }); thread.Start(); foreach (int i in Enumerable.Range(1, 1000)) { Shield.InTransaction(() => { testField.Modify((ref int n) => n++); }); } thread.Join(); Assert.AreEqual(0, failCommitCount); Assert.AreEqual(0, failVisibleCount); }
public void CommuteInvariantProblem() { // since pre-commits have no limitations on access, they cannot safely // execute within the commute sub-transaction. if they would, then this // test would fail on the last assertion. instead, pre-commits cause commutes // to degenerate. var testField = new Shielded<int>(); var effectField = new Shielded<int>(); var failCommitCount = new Shielded<int>(); var failVisibleCount = 0; // check if the effect field was written to, that the testField is even. Shield.PreCommit(() => effectField > 0, () => { if ((testField & 1) == 1) { Interlocked.Increment(ref failVisibleCount); // this will always fail to commit, confirming that the transaction // is already bound to fail. but, the failVisibleCount will be >0. failCommitCount.Modify((ref int n) => n++); } }); var thread = new Thread(() => { // if the testField is even, increment the effectField commutatively. foreach (int i in Enumerable.Range(1, 1000)) Shield.InTransaction(() => { if ((testField & 1) == 0) { effectField.Commute((ref int n) => n++); } }); }); thread.Start(); foreach (int i in Enumerable.Range(1, 1000)) Shield.InTransaction(() => { testField.Modify((ref int n) => n++); }); thread.Join(); Assert.AreEqual(0, failCommitCount); Assert.AreEqual(0, failVisibleCount); }
public void BasicCommuteTest() { var a = new Shielded <int>(); Shield.InTransaction(() => a.Commute((ref int n) => n++)); Assert.AreEqual(1, a); Shield.InTransaction(() => { Assert.AreEqual(1, a); a.Commute((ref int n) => n++); Assert.AreEqual(2, a); }); Assert.AreEqual(2, a); Shield.InTransaction(() => { a.Commute((ref int n) => n++); Assert.AreEqual(3, a); }); Assert.AreEqual(3, a); int transactionCount = 0, commuteCount = 0; ParallelEnumerable.Repeat(1, 100).ForAll(i => Shield.InTransaction(() => { Interlocked.Increment(ref transactionCount); a.Commute((ref int n) => { Interlocked.Increment(ref commuteCount); Thread.Sleep(10); // needs this.. (running on Mono 2.10) n++; }); })); Assert.AreEqual(103, a); // commutes never conflict (!) Assert.AreEqual(100, transactionCount); Assert.Greater(commuteCount, 100); Shield.InTransaction(() => { a.Commute((ref int n) => n -= 3); a.Commute((ref int n) => n--); }); Assert.AreEqual(99, a); }
public void CommuteTest() { var a = new Shielded<int>(); Shield.InTransaction(() => a.Commute((ref int n) => n++)); Assert.AreEqual(1, a); Shield.InTransaction(() => { Assert.AreEqual(1, a); a.Commute((ref int n) => n++); Assert.AreEqual(2, a); }); Assert.AreEqual(2, a); Shield.InTransaction(() => { a.Commute((ref int n) => n++); Assert.AreEqual(3, a); }); Assert.AreEqual(3, a); int transactionCount = 0, commuteCount = 0; ParallelEnumerable.Repeat(1, 100).ForAll(i => Shield.InTransaction(() => { Interlocked.Increment(ref transactionCount); a.Commute((ref int n) => { Interlocked.Increment(ref commuteCount); Thread.Sleep(10); // needs this.. (running on Mono 2.10) n++; }); })); Assert.AreEqual(103, a); // commutes never conflict (!) Assert.AreEqual(100, transactionCount); Assert.Greater(commuteCount, 100); Shield.InTransaction(() => { a.Commute((ref int n) => n -= 3); a.Commute((ref int n) => n--); }); Assert.AreEqual(99, a); }
static void SimpleOps() { long time; _timer = Stopwatch.StartNew(); var numItems = 1000000; var repeatsPerTrans = 50; Console.WriteLine( "Testing simple ops with {0} iterations, and repeats per trans (N) = {1}", numItems, repeatsPerTrans); var accessTest = new Shielded<int>(); Timed("WARM UP", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { accessTest.Value = 3; var a = accessTest.Value; accessTest.Modify((ref int n) => n = 5); a = accessTest.Value; }); }); var emptyTime = Timed("empty transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { }); }); var emptyReturningTime = Timed("1 empty transaction w/ result", numItems, () => { // this version uses the generic, result-returning InTransaction, which involves creation // of a closure, i.e. an allocation. foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => 5); }); var outOfTrReadTime = Timed("N out-of-tr. reads", numItems, () => { // this version uses the generic, result-returning InTransaction, which involves creation // of a closure, i.e. an allocation. foreach (var k in Enumerable.Repeat(1, numItems * repeatsPerTrans)) { var a = accessTest.Value; } }); // the purpose here is to get a better picture of the expense of using Shielded. a more // complex project would probably, during one transaction, repeatedly access the same // field. does this cost much more than a single-access transaction? if it is the same // field, then any significant extra expense is unacceptable. var oneReadTime = Timed("1-read transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { var a = accessTest.Value; }); }); var nReadTime = Timed("N-reads transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { int a; for (int i = 0; i < repeatsPerTrans; i++) a = accessTest.Value; }); }); var oneReadModifyTime = Timed("1-read-1-modify tr.", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { var a = accessTest.Value; accessTest.Modify((ref int n) => n = 1); }); }); // Assign is no longer commutable, for performance reasons. It is faster, // particularly when repeated (almost 10 times), and you can see the difference // that not reading the old value does. var oneReadAssignTime = Timed("1-read-1-assign tr.", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { var a = accessTest.Value; accessTest.Value = 1; }); }); var oneModifyTime = Timed("1-modify transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => accessTest.Modify((ref int n) => n = 1)); }); var nModifyTime = Timed("N-modify transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { for (int i = 0; i < repeatsPerTrans; i++) accessTest.Modify((ref int n) => n = 1); }); }); var accessTest2 = new Shielded<int>(); var modifyModifyTime = Timed("modify-modify transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { accessTest.Modify((ref int n) => n = 1); accessTest2.Modify((ref int n) => n = 2); }); }); // here Modify is the first call, making all Reads as fast as can be, // reading direct from local storage. var oneModifyNReadTime = Timed("1-modify-N-reads tr.", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { accessTest.Modify((ref int n) => n = 1); int a; for (int i = 0; i < repeatsPerTrans; i++) a = accessTest.Value; }); }); var oneAssignTime = Timed("1-assign transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => accessTest.Value = 1); }); var nAssignTime = Timed("N-assigns transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => { for (int i = 0; i < repeatsPerTrans; i++) accessTest.Value = 1; }); }); var oneCommuteTime = Timed("1-commute transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => accessTest.Commute((ref int n) => n = 1)); }); var nCommuteTime = Timed("N-commute transactions", numItems, () => { foreach (var k in Enumerable.Repeat(1, numItems/10)) Shield.InTransaction(() => { for (int i = 0; i < repeatsPerTrans; i++) accessTest.Commute((ref int n) => n = 1); }); }); Console.WriteLine("\ncost of empty transaction = {0:0.000} us", emptyTime / (numItems / 1000.0)); Console.WriteLine("cost of the closure in InTransaction<T> = {0:0.000} us", (emptyReturningTime - emptyTime) / (numItems / 1000.0)); Console.WriteLine("cost of an out-of-tr. read = {0:0.000} us", outOfTrReadTime * 1000.0 / (numItems * repeatsPerTrans)); Console.WriteLine("cost of the first read = {0:0.000} us", (oneReadTime - emptyTime) / (numItems / 1000.0)); Console.WriteLine("cost of an additional read = {0:0.000} us", (nReadTime - oneReadTime) / ((repeatsPerTrans - 1) * numItems / 1000.0)); Console.WriteLine("cost of Modify after read = {0:0.000} us", (oneReadModifyTime - oneReadTime) / (numItems / 1000.0)); Console.WriteLine("cost of Assign after read = {0:0.000} us", (oneReadAssignTime - oneReadTime) / (numItems / 1000.0)); Console.WriteLine("cost of the first Modify = {0:0.000} us", (oneModifyTime - emptyTime) / (numItems / 1000.0)); Console.WriteLine("cost of an additional Modify = {0:0.000} us", (nModifyTime - oneModifyTime) / ((repeatsPerTrans - 1) * numItems / 1000.0)); Console.WriteLine("cost of a second, different Modify = {0:0.000} us", (modifyModifyTime - oneModifyTime) / (numItems / 1000.0)); Console.WriteLine("cost of a read after Modify = {0:0.000} us", (oneModifyNReadTime - oneModifyTime) / (repeatsPerTrans * numItems / 1000.0)); Console.WriteLine("cost of the first Assign = {0:0.000} us", (oneAssignTime - emptyTime) / (numItems / 1000.0)); Console.WriteLine("cost of an additional Assign = {0:0.000} us", (nAssignTime - oneAssignTime) / ((repeatsPerTrans - 1) * numItems / 1000.0)); Console.WriteLine("cost of the first commute = {0:0.000} us", (oneCommuteTime - emptyTime) / (numItems / 1000.0)); Console.WriteLine("cost of an additional commute = {0:0.000} us", (nCommuteTime*10 - oneCommuteTime) / ((repeatsPerTrans - 1) * numItems / 1000.0)); }
public void ComplexCommute() { // some more complex commute combinations. first, with ShieldedSeq ops. var seq = new ShieldedSeq <int>(); // just a test for proper commute ordering Shield.InTransaction(() => { seq.Append(1); seq.Append(2); seq.Append(3); seq.Remove(2); Assert.IsTrue(seq.Any()); Assert.AreEqual(2, seq.Count); Assert.AreEqual(1, seq[0]); Assert.AreEqual(3, seq[1]); }); // a test for commutability of Append() Shield.InTransaction(() => { seq.Clear(); }); int transactionCount = 0; Thread oneTimer = null; Shield.InTransaction(() => { transactionCount++; seq.Append(1); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(2); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(1, transactionCount); Assert.AreEqual(2, seq.Count); // the "subthread" commited the append first, so: Assert.AreEqual(2, seq[0]); Assert.AreEqual(1, seq[1]); Assert.IsTrue(seq.Any()); // test for a commute degeneration - reading the head of a list causes // appends done in the same transaction to stop being commutable. for // simplicity - you could continue from the head on to the tail, and it cannot // thus be a commute, you read it. it could, of course, be done that it still // is a commute, but that would make it pretty complicated. Shield.InTransaction(() => { seq.Clear(); seq.Append(1); seq.Append(2); }); Assert.AreEqual(1, seq[0]); Assert.AreEqual(2, seq[1]); transactionCount = 0; oneTimer = null; Shield.InTransaction(() => { transactionCount++; Assert.AreEqual(1, seq.TakeHead()); seq.Append(4); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(3); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(2, transactionCount); Assert.AreEqual(3, seq.Count); Assert.IsTrue(seq.Any()); Assert.AreEqual(2, seq[0]); Assert.AreEqual(3, seq[1]); Assert.AreEqual(4, seq[2]); Shield.InTransaction(() => { seq.Clear(); seq.Append(1); seq.Append(2); }); Assert.AreEqual(1, seq[0]); Assert.AreEqual(2, seq[1]); transactionCount = 0; oneTimer = null; Shield.InTransaction(() => { // if we switch the order, doesn't matter. transactionCount++; seq.Append(4); Assert.AreEqual(1, seq.TakeHead()); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(3); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(2, transactionCount); Assert.AreEqual(3, seq.Count); Assert.IsTrue(seq.Any()); Assert.AreEqual(2, seq[0]); Assert.AreEqual(3, seq[1]); Assert.AreEqual(4, seq[2]); // here the removal takes out the last element in the list. this absolutely cannot // commute, because it read from the only element's Next field, and the Seq // knew that it was the last element. it must conflict. Shield.InTransaction(() => { seq.Clear(); seq.Append(1); }); Assert.AreEqual(1, seq[0]); transactionCount = 0; oneTimer = null; Shield.InTransaction(() => { transactionCount++; Assert.AreEqual(1, seq.TakeHead()); seq.Append(3); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(2); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(2, transactionCount); Assert.AreEqual(2, seq.Count); Assert.IsTrue(seq.Any()); Assert.AreEqual(2, seq[0]); Assert.AreEqual(3, seq[1]); // it is not allowed to read another Shielded from a Shielded.Commute()! // this greatly simplifies things. if you still want to use a value from another // Shielded, you must read it in main trans, forcing it's commutes to degenerate. var a = new Shielded <int>(); var b = new Shielded <int>(); Assert.Throws <InvalidOperationException>(() => Shield.InTransaction(() => { a.Commute((ref int n) => n = 1); b.Commute((ref int n) => n = a); })); Assert.Throws <InvalidOperationException>(() => Shield.InTransaction(() => { a.Commute((ref int n) => n = 1); b.Commute((ref int n) => { n = 1; a.Commute((ref int n2) => n2 = 2); }); })); Shield.InTransaction(() => { a.Commute((ref int n) => n = 1); b.Commute((ref int n) => n = a); Assert.Throws <InvalidOperationException>(() => { var x = b.Value; }); }); }
public void DegeneratingCommuteTest() { var a = new Shielded<int>(); int numInc = 100000; int transactionCount = 0, commuteCount = 0; ParallelEnumerable.Repeat(1, numInc/2).ForAll(i => Shield.InTransaction(() => { Interlocked.Increment(ref transactionCount); a.Commute((ref int n) => { Interlocked.Increment(ref commuteCount); n++; }); a.Commute((ref int n) => { Interlocked.Increment(ref commuteCount); n++; }); // so, we cause it to degenerate. there was a subtle bug in enlisting which // would allow a degenerated commute to execute before checking the lock! int x = a; })); Assert.AreEqual(numInc, a); // degenerated commutes conflict, which means transaction will repeat. conflict // may be detected before the commute lambda actually gets to execute, so the // trans count can be greater than commute count. Assert.GreaterOrEqual(transactionCount, commuteCount/2); // classic, just to confirm there was at least one conflict. Assert.Greater(commuteCount, numInc); }
public static void SimpleCommuteTest() { var a = new Shielded<int>(); Shield.InTransaction(() => a.Commute((ref int n) => n++)); Console.WriteLine(a); Shield.InTransaction(() => { Console.WriteLine(a); a.Commute((ref int n) => n++); Console.WriteLine(a); }); Console.WriteLine(a); Shield.InTransaction(() => { a.Commute((ref int n) => n++); Console.WriteLine(a); }); Console.WriteLine(a); }
public void ComplexCommute() { // some more complex commute combinations. first, with ShieldedSeq ops. var seq = new ShieldedSeq<int>(); Shield.InTransaction(() => { // test for potential disorder of the seq commutes. seq.Append(1); seq.Clear(); Assert.IsFalse(seq.HasAny); Assert.AreEqual(0, seq.Count); }); Shield.InTransaction(() => { seq.Append(1); seq.Append(2); seq.Append(3); seq.Remove(2); Assert.IsTrue(seq.HasAny); Assert.AreEqual(2, seq.Count); Assert.AreEqual(1, seq[0]); Assert.AreEqual(3, seq[1]); seq.Clear(); Assert.IsFalse(seq.HasAny); Assert.AreEqual(0, seq.Count); }); Shield.InTransaction(() => { seq.Append(1); seq.Append(2); }); Assert.AreEqual(1, seq[0]); Assert.AreEqual(2, seq[1]); int transactionCount = 0; Thread oneTimer = null; Shield.InTransaction(() => { // here's a weird one - the seq is only partially commuted, due to reading // from the head, but it still commutes with a trans that is only appending. transactionCount++; Assert.AreEqual(1, seq.TakeHead()); // Count or tail were not read! Clearing can commute with appending. seq.Clear(); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(3); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(1, transactionCount); Assert.AreEqual(0, seq.Count); Assert.IsFalse(seq.HasAny); Shield.InTransaction(() => { seq.Append(1); seq.Append(2); }); Assert.AreEqual(1, seq[0]); Assert.AreEqual(2, seq[1]); transactionCount = 0; oneTimer = null; Shield.InTransaction(() => { // same as above, but with appending, does not work. reading the _head screws it up, // because you could continue from the head to the last item. transactionCount++; Assert.AreEqual(1, seq.TakeHead()); seq.Append(4); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(3); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(2, transactionCount); Assert.AreEqual(3, seq.Count); Assert.IsTrue(seq.HasAny); Assert.AreEqual(2, seq[0]); Assert.AreEqual(3, seq[1]); Assert.AreEqual(4, seq[2]); Shield.InTransaction(() => { seq.Clear(); seq.Append(1); seq.Append(2); }); Assert.AreEqual(1, seq[0]); Assert.AreEqual(2, seq[1]); transactionCount = 0; oneTimer = null; Shield.InTransaction(() => { // if we switch the order, doesn't matter. transactionCount++; seq.Append(4); Assert.AreEqual(1, seq.TakeHead()); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(3); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(2, transactionCount); Assert.AreEqual(3, seq.Count); Assert.IsTrue(seq.HasAny); Assert.AreEqual(2, seq[0]); Assert.AreEqual(3, seq[1]); Assert.AreEqual(4, seq[2]); Shield.InTransaction(() => { seq.Clear(); seq.Append(1); }); Assert.AreEqual(1, seq[0]); transactionCount = 0; oneTimer = null; Shield.InTransaction(() => { // here the removal takes out the last element in the list. this cannot // commute, because it read from the only element's Next field, and the Seq // knew that it was the last element. it must conflict. transactionCount++; Assert.AreEqual(1, seq.TakeHead()); seq.Append(3); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(2); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(2, transactionCount); Assert.AreEqual(2, seq.Count); Assert.IsTrue(seq.HasAny); Assert.AreEqual(2, seq[0]); Assert.AreEqual(3, seq[1]); // it is not allowed to read another Shielded from a Shielded.Commute()! // this greatly simplifies things. if you still want to use a value from another // Shielded, you must read it in main trans, forcing it's commutes to degenerate. var a = new Shielded<int>(); var b = new Shielded<int>(); try { Shield.InTransaction(() => { a.Commute((ref int n) => n = 1); b.Commute((ref int n) => n = a); }); Assert.Fail(); } catch (InvalidOperationException) {} try { Shield.InTransaction(() => { a.Commute((ref int n) => n = 1); b.Commute((ref int n) => { n = 1; a.Commute((ref int n2) => n2 = 2); }); }); Assert.Fail(); } catch (InvalidOperationException) {} Shield.InTransaction(() => { a.Commute((ref int n) => n = 1); b.Commute((ref int n) => n = a); try { var x = b.Read; Assert.Fail(); } catch (InvalidOperationException) {} }); }
public void EventTest() { var a = new Shielded<int>(1); var eventCount = new Shielded<int>(); EventHandler<EventArgs> ev = (sender, arg) => eventCount.Commute((ref int e) => e++); try { a.Changed.Subscribe(ev); Assert.Fail(); } catch (InvalidOperationException) {} Shield.InTransaction(() => { a.Changed.Subscribe(ev); var t = new Thread(() => { Shield.InTransaction(() => { a.Modify((ref int x) => x++); }); }); t.Start(); t.Join(); var t2 = new Thread(() => { Shield.InTransaction(() => { a.Modify((ref int x) => x++); }); }); t2.Start(); t2.Join(); }); Assert.AreEqual(0, eventCount); Shield.InTransaction(() => { a.Modify((ref int x) => x++); }); Assert.AreEqual(1, eventCount); Thread tUnsub = null; Shield.InTransaction(() => { a.Changed.Unsubscribe(ev); a.Modify((ref int x) => x++); if (tUnsub == null) { tUnsub = new Thread(() => { Shield.InTransaction(() => { a.Modify((ref int x) => x++); a.Modify((ref int x) => x++); }); }); tUnsub.Start(); tUnsub.Join(); } }); // the other thread must still see the subscription... Assert.AreEqual(3, eventCount); Shield.InTransaction(() => a.Modify((ref int x) => x++)); Assert.AreEqual(3, eventCount); }
public static void BetShopPoolTest() { int numThreads = 3; int numTickets = 200000; int numEvents = 100; var barrier = new Barrier(2); var betShop = new BetShop(numEvents); var randomizr = new Random(); var bags = new List<Action>[numThreads]; var threads = new Thread[numThreads]; for (int i = 0; i < numThreads; i++) { var bag = bags[i] = new List<Action>(); threads[i] = new Thread(() => { foreach (var a in bag) a(); }); } var complete = new Shielded<int>(); IDisposable completeCond = null; completeCond = Shield.Conditional(() => complete == numTickets, () => { barrier.SignalAndWait(); completeCond.Dispose(); }); var reportEvery = 10000; Shielded<int> lastReport = new Shielded<int>(0); Shielded<DateTime> lastTime = new Shielded<DateTime>(DateTime.UtcNow); using (Shield.Conditional( () => betShop.Tickets.Count >= lastReport + reportEvery, () => { DateTime newNow = DateTime.UtcNow; int count = betShop.Tickets.Count; int speed = (count - lastReport) * 1000 / (int)newNow.Subtract(lastTime).TotalMilliseconds; lastTime.Value = newNow; lastReport.Modify((ref int n) => n += reportEvery); Shield.SideEffect(() => Console.Write("\n{0} at {1} item/s", count, speed)); })) { foreach (var i in Enumerable.Range(0, numTickets)) { decimal payIn = (randomizr.Next(10) + 1m) * 1; int event1Id = randomizr.Next(numEvents) + 1; int event2Id = randomizr.Next(numEvents) + 1; int event3Id = randomizr.Next(numEvents) + 1; int offer1Ind = randomizr.Next(3); int offer2Ind = randomizr.Next(3); int offer3Ind = randomizr.Next(3); bags[i % numThreads].Add(() => Shield.InTransaction(() => { var offer1 = betShop.Events[event1Id].BetOffers[offer1Ind]; var offer2 = betShop.Events[event2Id].BetOffers[offer2Ind]; var offer3 = betShop.Events[event3Id].BetOffers[offer3Ind]; betShop.BuyTicket(payIn, offer1, offer2, offer3); complete.Commute((ref int n) => n++); })); } _timer = Stopwatch.StartNew(); for (int i = 0; i < numThreads; i++) threads[i].Start(); barrier.SignalAndWait(); } var time = _timer.ElapsedMilliseconds; var totalCorrect = betShop.VerifyTickets(); Console.WriteLine(" {0} ms with {1} tickets paid in and is {2}.", time, betShop.Tickets.Count, totalCorrect ? "correct" : "incorrect"); }
public void ComplexCommute() { // some more complex commute combinations. first, with ShieldedSeq ops. var seq = new ShieldedSeq<int>(); // just a test for proper commute ordering Shield.InTransaction(() => { seq.Append(1); seq.Append(2); seq.Append(3); seq.Remove(2); Assert.IsTrue(seq.Any()); Assert.AreEqual(2, seq.Count); Assert.AreEqual(1, seq[0]); Assert.AreEqual(3, seq[1]); }); // a test for commutability of Append() Shield.InTransaction(() => { seq.Clear(); }); int transactionCount = 0; Thread oneTimer = null; Shield.InTransaction(() => { transactionCount++; seq.Append(1); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(2); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(1, transactionCount); Assert.AreEqual(2, seq.Count); // the "subthread" commited the append first, so: Assert.AreEqual(2, seq[0]); Assert.AreEqual(1, seq[1]); Assert.IsTrue(seq.Any()); // test for a commute degeneration - reading the head of a list causes // appends done in the same transaction to stop being commutable. for // simplicity - you could continue from the head on to the tail, and it cannot // thus be a commute, you read it. it could, of course, be done that it still // is a commute, but that would make it pretty complicated. Shield.InTransaction(() => { seq.Clear(); seq.Append(1); seq.Append(2); }); Assert.AreEqual(1, seq[0]); Assert.AreEqual(2, seq[1]); transactionCount = 0; oneTimer = null; Shield.InTransaction(() => { transactionCount++; Assert.AreEqual(1, seq.TakeHead()); seq.Append(4); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(3); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(2, transactionCount); Assert.AreEqual(3, seq.Count); Assert.IsTrue(seq.Any()); Assert.AreEqual(2, seq[0]); Assert.AreEqual(3, seq[1]); Assert.AreEqual(4, seq[2]); Shield.InTransaction(() => { seq.Clear(); seq.Append(1); seq.Append(2); }); Assert.AreEqual(1, seq[0]); Assert.AreEqual(2, seq[1]); transactionCount = 0; oneTimer = null; Shield.InTransaction(() => { // if we switch the order, doesn't matter. transactionCount++; seq.Append(4); Assert.AreEqual(1, seq.TakeHead()); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(3); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(2, transactionCount); Assert.AreEqual(3, seq.Count); Assert.IsTrue(seq.Any()); Assert.AreEqual(2, seq[0]); Assert.AreEqual(3, seq[1]); Assert.AreEqual(4, seq[2]); // here the removal takes out the last element in the list. this absolutely cannot // commute, because it read from the only element's Next field, and the Seq // knew that it was the last element. it must conflict. Shield.InTransaction(() => { seq.Clear(); seq.Append(1); }); Assert.AreEqual(1, seq[0]); transactionCount = 0; oneTimer = null; Shield.InTransaction(() => { transactionCount++; Assert.AreEqual(1, seq.TakeHead()); seq.Append(3); if (oneTimer == null) { oneTimer = new Thread(() => Shield.InTransaction(() => { seq.Append(2); })); oneTimer.Start(); oneTimer.Join(); } }); Assert.AreEqual(2, transactionCount); Assert.AreEqual(2, seq.Count); Assert.IsTrue(seq.Any()); Assert.AreEqual(2, seq[0]); Assert.AreEqual(3, seq[1]); // it is not allowed to read another Shielded from a Shielded.Commute()! // this greatly simplifies things. if you still want to use a value from another // Shielded, you must read it in main trans, forcing it's commutes to degenerate. var a = new Shielded<int>(); var b = new Shielded<int>(); Assert.Throws<InvalidOperationException>(() => Shield.InTransaction(() => { a.Commute((ref int n) => n = 1); b.Commute((ref int n) => n = a); })); Assert.Throws<InvalidOperationException>(() => Shield.InTransaction(() => { a.Commute((ref int n) => n = 1); b.Commute((ref int n) => { n = 1; a.Commute((ref int n2) => n2 = 2); }); })); Shield.InTransaction(() => { a.Commute((ref int n) => n = 1); b.Commute((ref int n) => n = a); Assert.Throws<InvalidOperationException>(() => { var x = b.Value; }); }); }