private void CountTracking() { int reportEvery = 10000; Shielded<int> lastReport = new Shielded<int>(0); Shielded<DateTime> lastTime = new Shielded<DateTime>(DateTime.UtcNow); Shield.Conditional(() => _processed >= lastReport + reportEvery, () => { DateTime newNow = DateTime.UtcNow; int count = _processed; int speed = (count - lastReport) * 1000 / (int)newNow.Subtract(lastTime).TotalMilliseconds; lastTime.Assign(newNow); lastReport.Modify((ref int n) => n += reportEvery); int sc = _subscribeCount; int ptc = _processTestCount; int pbc = _processBodyCount; Shield.SideEffect(() => { Console.WriteLine( "{0} at {1} item/s, stats ( {2}, {3}, {4} )", count, speed, sc, ptc, pbc); }); return true; }); }
public void ConditionalTest() { var x = new Shielded<int>(); var testCounter = 0; var triggerCommits = 0; Shield.Conditional(() => { Interlocked.Increment(ref testCounter); return x > 0 && (x & 1) == 0; }, () => { Shield.SideEffect(() => Interlocked.Increment(ref triggerCommits)); Assert.IsTrue(x > 0 && (x & 1) == 0); }); const int count = 1000; ParallelEnumerable.Repeat(1, count).ForAll(i => Shield.InTransaction(() => x.Modify((ref int n) => n++))); // one more, for the first call to Conditional()! btw, if this conditional were to // write anywhere, he might conflict, and an interlocked counter would give more due to // repetitions. so, this confirms reader progress too. Assert.AreEqual(count + 1, testCounter); // every change triggers it, but by the time it starts, another transaction might have // committed, so this is not a fixed number. Assert.Greater(triggerCommits, 0); // a conditional which does not depend on any Shielded is not allowed! int a = 5; Assert.Throws<InvalidOperationException>(() => Shield.Conditional(() => a > 10, () => { })); bool firstTime = true; var x2 = new Shielded<int>(); // this one succeeds in registering, but fails as soon as it gets triggered, due to changing it's // test's access pattern to an empty set. Shield.Conditional(() => { if (firstTime) { firstTime = false; return x2 == 0; } else // this is of course invalid, and when reaching here we have not touched any Shielded obj. return true; }, () => { }); try { // this will trigger the conditional Shield.InTransaction(() => x2.Modify((ref int n) => n++)); Assert.Fail(); } catch (AggregateException aggr) { Assert.AreEqual(1, aggr.InnerExceptions.Count); Assert.AreEqual(typeof(InvalidOperationException), aggr.InnerException.GetType()); } }
public void SkewWriteTest() { var cats = new Shielded <int>(1); var dogs = new Shielded <int>(1); int transactionCount = 0; Task.WaitAll( Enumerable.Range(1, 2).Select(i => Task.Factory.StartNew(() => Shield.InTransaction(() => { Interlocked.Increment(ref transactionCount); if (cats + dogs < 3) { Thread.Sleep(200); if (i == 1) { cats.Modify((ref int n) => n++); } else { dogs.Modify((ref int n) => n++); } } }), TaskCreationOptions.LongRunning)).ToArray()); Assert.AreEqual(3, cats + dogs); Assert.AreEqual(3, transactionCount); }
public void SideEffectTest() { var x = new Shielded <DateTime>(DateTime.UtcNow); try { Shield.InTransaction(() => { Shield.SideEffect(() => { Assert.Fail("Suicide transaction has committed."); }, () => { throw new IgnoreMe(); }); // in case Assign() becomes commutative, we use Modify() to ensure conflict. x.Modify((ref DateTime d) => d = DateTime.UtcNow); var t = new Thread(() => Shield.InTransaction(() => x.Modify((ref DateTime d) => d = DateTime.UtcNow))); t.Start(); t.Join(); }); Assert.Fail("Suicide transaction did not throw."); } catch (AggregateException aggr) { Assert.AreEqual(1, aggr.InnerExceptions.Count); Assert.AreEqual(typeof(IgnoreMe), aggr.InnerException.GetType()); } bool commitFx = false; Shield.InTransaction(() => { Shield.SideEffect(() => { Assert.IsFalse(commitFx); commitFx = true; }); }); Assert.IsTrue(commitFx); bool outOfTransFx = false, outOfTransOnRollback = false; Shield.SideEffect(() => outOfTransFx = true, () => outOfTransOnRollback = true); Assert.IsTrue(outOfTransFx); Assert.IsFalse(outOfTransOnRollback); }
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 TransactionSafetyTest() { Shielded <int> a = new Shielded <int>(5); Assert.AreEqual(5, a); Assert.Throws <InvalidOperationException>(() => a.Modify((ref int n) => n = 10)); Assert.IsFalse(Shield.IsInTransaction); Shield.InTransaction(() => { a.Modify((ref int n) => n = 20); // the TPL sometimes executes tasks on the same thread. int x1 = 0; var t = new Thread(() => { Assert.IsFalse(Shield.IsInTransaction); x1 = a; }); t.Start(); t.Join(); Assert.IsTrue(Shield.IsInTransaction); Assert.AreEqual(5, x1); Assert.AreEqual(20, a); }); Assert.IsFalse(Shield.IsInTransaction); int x2 = 0; var t2 = new Thread(() => { Assert.IsFalse(Shield.IsInTransaction); x2 = a; }); t2.Start(); t2.Join(); Assert.AreEqual(20, x2); Assert.AreEqual(20, a); }
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 BasicTest() { var a = new Shielded<int>(10); Shield.InTransaction(() => { a.Value = 20; Assert.AreEqual(20, a); Shield.ReadOldState(() => { Assert.AreEqual(10, a); a.Value = 30; Assert.AreEqual(10, a); a.Modify((ref int x) => Assert.AreEqual(30, x)); }); Assert.AreEqual(30, a.Value); }); Assert.AreEqual(30, a.Value); }
public void BasicTest() { var a = new Shielded <int>(10); Shield.InTransaction(() => { a.Value = 20; Assert.AreEqual(20, a); Shield.ReadOldState(() => { Assert.AreEqual(10, a); a.Value = 30; Assert.AreEqual(10, a); a.Modify((ref int x) => Assert.AreEqual(30, x)); }); Assert.AreEqual(30, a.Value); }); Assert.AreEqual(30, a.Value); }
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 TransactionSafetyTest() { Shielded<int> a = new Shielded<int>(5); Assert.AreEqual(5, a); try { a.Modify((ref int n) => n = 10); Assert.Fail(); } catch (InvalidOperationException) {} Assert.IsFalse(Shield.IsInTransaction); Shield.InTransaction(() => { a.Modify((ref int n) => n = 20); // the TPL sometimes executes tasks on the same thread. int x1 = 0; var t = new Thread(() => { Assert.IsFalse(Shield.IsInTransaction); x1 = a; }); t.Start(); t.Join(); Assert.IsTrue(Shield.IsInTransaction); Assert.AreEqual(5, x1); Assert.AreEqual(20, a); }); Assert.IsFalse(Shield.IsInTransaction); int x2 = 0; var t2 = new Thread(() => { Assert.IsFalse(Shield.IsInTransaction); x2 = a; }); t2.Start(); t2.Join(); Assert.AreEqual(20, x2); Assert.AreEqual(20, a); }
public void SkewWriteTest() { var cats = new Shielded<int>(1); var dogs = new Shielded<int>(1); int transactionCount = 0; Task.WaitAll( Enumerable.Range(1, 2).Select(i => Task.Factory.StartNew(() => Shield.InTransaction(() => { Interlocked.Increment(ref transactionCount); if (cats + dogs < 3) { Thread.Sleep(200); if (i == 1) cats.Modify((ref int n) => n++); else dogs.Modify((ref int n) => n++); } }), TaskCreationOptions.LongRunning)).ToArray()); Assert.AreEqual(3, cats + dogs); Assert.AreEqual(3, transactionCount); }
public void SideEffectTest() { var x = new Shielded<DateTime>(DateTime.UtcNow); try { Shield.InTransaction(() => { Shield.SideEffect(() => { Assert.Fail("Suicide transaction has committed."); }, () => { throw new IgnoreMe(); }); // in case Assign() becomes commutative, we use Modify() to ensure conflict. x.Modify((ref DateTime d) => d = DateTime.UtcNow); var t = new Thread(() => Shield.InTransaction(() => x.Modify((ref DateTime d) => d = DateTime.UtcNow))); t.Start(); t.Join(); }); Assert.Fail("Suicide transaction did not throw."); } catch (IgnoreMe) {} bool commitFx = false; Shield.InTransaction(() => { Shield.SideEffect(() => { Assert.IsFalse(commitFx); commitFx = true; }); }); Assert.IsTrue(commitFx); }
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); }
/// <summary> /// Creates n events, with three typical offers (1,X,2) for each. /// The events get IDs 1-n. /// </summary> public BetShop(int n) { List<Shielded<Event>> initialEvents = new List<Shielded<Event>>(); Shield.InTransaction(() => { int eventIdGenerator = 1; int offerIdGenerator = 1; for (int i = 0; i < n; i++) { var newEvent = new Shielded<Event>(new Event() { Id = eventIdGenerator++, HomeTeam = "Home " + i, AwayTeam = "Away " + i }); // we have to use Modify, because each offer needs a ref to the shielded // event, which we do not have before that shielded event is constructed. And, // after he is constructed, he can only be changed like this. newEvent.Modify((ref Event e) => e.BetOffers = new ShieldedSeq<Shielded<BetOffer>>( new Shielded<BetOffer>(new BetOffer() { Id = offerIdGenerator++, Event = newEvent, Pick = "1", Odds = 2m }), new Shielded<BetOffer>(new BetOffer() { Id = offerIdGenerator++, Event = newEvent, Pick = "X", Odds = 4m }), new Shielded<BetOffer>(new BetOffer() { Id = offerIdGenerator++, Event = newEvent, Pick = "2", Odds = 4.5m }))); initialEvents.Add(newEvent); } }); Events = new ShieldedDict<int, Shielded<Event>>( initialEvents.Select(e => new KeyValuePair<int, Shielded<Event>>(e.Read.Id, e))); }
public void Prioritization() { var x = new Shielded<int>(); var barrier = new Barrier(2); int slowThread1Repeats = 0; var slowThread1 = new Thread(() => { barrier.SignalAndWait(); Shield.InTransaction(() => { Interlocked.Increment(ref slowThread1Repeats); int a = x; Thread.Sleep(100); x.Value = a - 1; }); }); slowThread1.Start(); IDisposable conditional = null; conditional = Shield.Conditional(() => { int i = x; return true; }, () => { barrier.SignalAndWait(); Thread.Yield(); conditional.Dispose(); }); foreach (int i in Enumerable.Range(1, 1000)) { Shield.InTransaction(() => { x.Modify((ref int a) => a++); }); } slowThread1.Join(); Assert.Greater(slowThread1Repeats, 1); Assert.AreEqual(999, x); // now, we introduce prioritization. // this condition gets triggered before any attempt to write into x int ownerThreadId = -1; Shield.PreCommit(() => { int a = x; return true; }, () => { var threadId = ownerThreadId; if (threadId > -1 && threadId != Thread.CurrentThread.ManagedThreadId) // we'll cause lower prio threads to busy wait. we could also // add, e.g., an onRollback SideEffect which would wait for // a certain signal before continuing the next iteration.. // (NB that Shield.SideEffect would, of course, have to be called // before calling Rollback.) Shield.Rollback(); }); // this will pass due to ownerThreadId == -1 Shield.InTransaction(() => x.Value = 0); int slowThread2Repeats = 0; var slowThread2 = new Thread(() => { try { barrier.SignalAndWait(); Interlocked.Exchange(ref ownerThreadId, Thread.CurrentThread.ManagedThreadId); Shield.InTransaction(() => { Interlocked.Increment(ref slowThread2Repeats); int a = x; Thread.Sleep(100); x.Value = a - 1; }); } finally { Interlocked.Exchange(ref ownerThreadId, -1); } }); slowThread2.Start(); conditional = Shield.Conditional(() => { int i = x; return true; }, () => { barrier.SignalAndWait(); conditional.Dispose(); }); foreach (int i in Enumerable.Range(1, 1000)) { Shield.InTransaction(() => { x.Modify((ref int a) => a++); }); } slowThread2.Join(); Assert.AreEqual(1, slowThread2Repeats); Assert.AreEqual(999, x); }
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)); }
static void ControlledRace() { var acc1 = new Shielded<Account>(new Account() { Id = 1, Balance = 1000M, Transfers = new List<Transfer>() }); var acc2 = new Shielded<Account>(new Account() { Id = 2, Balance = 1000M, Transfers = new List<Transfer>() }); int transactionCount = 0; mtTest("controlled race", 20, n => { if (n % 2 == 0) return Task.Factory.StartNew(() => { Shield.InTransaction(() => { Interlocked.Increment(ref transactionCount); Shield.SideEffect(() => Console.WriteLine("Transferred 100.00 .. acc1 -> acc2"), () => Console.WriteLine("Task 1 rollback!")); acc1.Modify((ref Account a) => { a.Balance = a.Balance - 100M; var list = a.Transfers; Shield.SideEffect(() => list.Add( new Transfer() { OtherId = acc2.Value.Id, AmountReceived = -100M })); }); Thread.Sleep(100); acc2.Modify((ref Account a) => { a.Balance = a.Balance + 100M; var list = a.Transfers; Shield.SideEffect(() => list.Add( new Transfer() { OtherId = acc1.Value.Id, AmountReceived = 100M })); }); }); }, TaskCreationOptions.LongRunning); else return Task.Factory.StartNew(() => { Shield.InTransaction(() => { Interlocked.Increment(ref transactionCount); Shield.SideEffect(() => Console.WriteLine("Transferred 200.00 .. acc1 <- acc2"), () => Console.WriteLine("Task 2 rollback!")); acc2.Modify((ref Account a) => { a.Balance = a.Balance - 200M; var list = a.Transfers; Shield.SideEffect(() => list.Add( new Transfer() { OtherId = acc1.Value.Id, AmountReceived = -200M })); }); Thread.Sleep(250); acc1.Modify((ref Account a) => { a.Balance = a.Balance + 200M; var list = a.Transfers; Shield.SideEffect(() => list.Add( new Transfer() { OtherId = acc2.Value.Id, AmountReceived = 200M })); }); }); }, TaskCreationOptions.LongRunning); }); Console.WriteLine("\nCompleted 20 transactions in {0} total attempts.", transactionCount); Console.WriteLine("Account 1 balance: {0}", acc1.Value.Balance); foreach (var t in acc1.Value.Transfers) { Console.WriteLine(" {0:####,00}", t.AmountReceived); } Console.WriteLine("\nAccount 2 balance: {0}", acc2.Value.Balance); foreach (var t in acc2.Value.Transfers) { Console.WriteLine(" {0:####,00}", t.AmountReceived); } }
public static void MultiFieldOps() { long time; _timer = Stopwatch.StartNew(); var numTrans = 100000; var fields = 20; Console.WriteLine( "Testing multi-field ops with {0} iterations, and nuber of fields (N) = {1}", numTrans, fields); var accessTest = new Shielded<int>[fields]; for (int i = 0; i < fields; i++) accessTest[i] = new Shielded<int>(); var dummy = new Shielded<int>(); time = _timer.ElapsedMilliseconds; foreach (var k in Enumerable.Repeat(1, numTrans)) Shield.InTransaction(() => { dummy.Value = 3; var a = dummy.Value; dummy.Modify((ref int n) => n = 5); a = dummy.Value; }); time = _timer.ElapsedMilliseconds - time; Console.WriteLine("WARM UP in {0} ms.", time); var results = new long[fields]; foreach (var i in Enumerable.Range(0, fields)) { time = _timer.ElapsedMilliseconds; foreach (var k in Enumerable.Repeat(1, numTrans)) Shield.InTransaction(() => { for (int j = 0; j <= i; j++) accessTest[j].Modify((ref int n) => n = 1); }); results[i] = _timer.ElapsedMilliseconds - time; Console.WriteLine("{0} field modifiers in {1} ms.", i + 1, results[i]); } }
public void Prioritization() { var x = new Shielded <int>(); var barrier = new Barrier(2); // first, the version with no prioritization. the slow thread will repeat. int slowThread1Repeats = 0; var slowThread1 = new Thread(() => { barrier.SignalAndWait(); Shield.InTransaction(() => { Interlocked.Increment(ref slowThread1Repeats); int a = x; Thread.Sleep(100); x.Value = a - 1; }); }); slowThread1.Start(); IDisposable conditional = null; conditional = Shield.Conditional(() => { int i = x; return(true); }, () => { barrier.SignalAndWait(); Thread.Yield(); conditional.Dispose(); }); foreach (int i in Enumerable.Range(1, 1000)) { Shield.InTransaction(() => { x.Modify((ref int a) => a++); }); } slowThread1.Join(); Assert.Greater(slowThread1Repeats, 1); Assert.AreEqual(999, x); // now, we introduce prioritization, using a simple lock var lockObj = new object(); // this condition gets triggered just before any attempt to commit into x Shield.PreCommit(() => { int a = x; return(true); }, () => { // the simplest way to block low prio writers is just: //lock (lockObj) { } // but then the actual commit happens outside of the lock and may yet // cause a conflict with someone just taking the lock. still, it's safer! // and might be good enough for cases where a repetition won't hurt. bool taken = false; Action release = () => { if (taken) { Monitor.Exit(lockObj); taken = false; } }; // a bit of extra safety by using sync for the commit case. Shield.SyncSideEffect(release); Shield.SideEffect(null, release); Monitor.Enter(lockObj, ref taken); }); // not yet locked, so this is ok. Shield.InTransaction(() => x.Value = 0); int slowThread2Repeats = 0; var slowThread2 = new Thread(() => { barrier.SignalAndWait(); lock (lockObj) { Shield.InTransaction(() => { Interlocked.Increment(ref slowThread2Repeats); int a = x; Thread.Sleep(100); x.Value = a - 1; }); } }); slowThread2.Start(); conditional = Shield.Conditional(() => { int i = x; return(true); }, () => { barrier.SignalAndWait(); conditional.Dispose(); }); foreach (int i in Enumerable.Range(1, 1000)) { Shield.InTransaction(() => { x.Modify((ref int a) => a++); }); } slowThread2.Join(); Assert.AreEqual(1, slowThread2Repeats); Assert.AreEqual(999, x); }
public static void TreePoolTest() { int numThreads = 4; int numItems = 200000; // for some reason, if this is replaced with ShieldedDict, KeyAlreadyPresent // exception is thrown. under one key you can then find an entity which does // not have that key. complete mystery. var tree = new ShieldedDict<Guid, TreeItem>(); var barrier = new Barrier(numThreads + 1); int reportEvery = 10000; Shielded<int> lastReport = new Shielded<int>(0); Shielded<DateTime> lastTime = new Shielded<DateTime>(DateTime.UtcNow); Shield.Conditional(() => tree.Count >= lastReport + reportEvery, () => { DateTime newNow = DateTime.UtcNow; int count = tree.Count; int speed = (count - lastReport) * 1000 / (int)newNow.Subtract(lastTime).TotalMilliseconds; lastTime.Assign(newNow); lastReport.Modify((ref int n) => n += reportEvery); Shield.SideEffect(() => { Console.Write("\n{0} at {1} item/s", count, speed); }); return true; }); TreeItem x = new TreeItem(); _timer = new Stopwatch(); _timer.Start(); var time = _timer.ElapsedMilliseconds; foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => x.Id); time = _timer.ElapsedMilliseconds - time; Console.WriteLine("1 read transactions in {0} ms.", time); 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) { try { a(); } catch { Console.Write(" * "); } } barrier.SignalAndWait(); }); } foreach (var i in Enumerable.Range(0, numItems)) { var item1 = new TreeItem(); bags[i % numThreads].Add(() => Shield.InTransaction(() => { tree.Add(item1.Id, item1); })); } for (int i = 0; i < numThreads; i++) threads[i].Start(); barrier.SignalAndWait(); time = _timer.ElapsedMilliseconds; Console.WriteLine(" {0} ms.", time); Console.WriteLine("\nReading sequentially..."); time = _timer.ElapsedMilliseconds; var keys = Shield.InTransaction(() => tree.Keys); time = _timer.ElapsedMilliseconds - time; Console.WriteLine("Keys read in {0} ms.", time); time = _timer.ElapsedMilliseconds; Shield.InTransaction(() => { foreach (var kvp in tree) x = kvp.Value; }); time = _timer.ElapsedMilliseconds - time; Console.WriteLine("Items read by enumerator in {0} ms.", time); time = _timer.ElapsedMilliseconds; Shield.InTransaction(() => { foreach (var k in keys) x = tree[k]; }); time = _timer.ElapsedMilliseconds - time; Console.WriteLine("Items read by key in one trans in {0} ms.", time); time = _timer.ElapsedMilliseconds; foreach (var k in keys) x = tree[k]; time = _timer.ElapsedMilliseconds - time; Console.WriteLine("Items read by key separately in {0} ms.", time); time = _timer.ElapsedMilliseconds; keys.AsParallel().ForAll(k => x = tree[k]); time = _timer.ElapsedMilliseconds - time; Console.WriteLine("Items read by key in parallel in {0} ms.", time); time = _timer.ElapsedMilliseconds; foreach (var k in Enumerable.Repeat(1, numItems)) Shield.InTransaction(() => x.Id); time = _timer.ElapsedMilliseconds - time; Console.WriteLine("1 read transactions in {0} ms.", time); }
public int? BuyTicket(decimal payIn, params Shielded<BetOffer>[] bets) { var newId = Interlocked.Increment(ref _ticketIdGenerator); var newTicket = new Shielded<Ticket>(new Ticket() { Id = newId, PayInAmount = payIn }); return Shield.InTransaction(() => { newTicket.Modify((ref Ticket t) => { t.Bets = bets.Select(shBo => new Bet() { Offer = shBo, Odds = shBo.Read.Odds }).ToArray(); t.WinAmount = t.PayInAmount * t.Bets.Aggregate(1m, (curr, nextBet) => curr * nextBet.Odds); }); var hash = GetOfferHash(newTicket); var totalWin = _sameTicketWins.ContainsKey(hash) ? _sameTicketWins[hash] + newTicket.Read.WinAmount : newTicket.Read.WinAmount; if (totalWin > SameTicketWinLimit) return false; Tickets[newId] = newTicket; _sameTicketWins[hash] = totalWin; return true; }) ? (int?)newId : null; }
public static void Take() { Shield.InTransaction(() => _count.Modify((ref int c) => c--)); }
/// <summary> /// Creates a BetShop, and tries to buy a large number of random tickets. Afterwards it /// checks that the rule limiting same ticket winnings is not violated. /// </summary> public static void BetShopTest() { int numEvents = 100; var betShop = new BetShop(numEvents); var randomizr = new Random(); int reportEvery = 1000; var lastReport = new Shielded<int>(0); var lastTime = new Shielded<DateTime>(DateTime.UtcNow); long time; using (var reportingCond = 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); }); })) { time = mtTest("bet shop w/ " + numEvents, 50000, i => { 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); return Task.Factory.StartNew(() => 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); })); }); } //} 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 SideEffectTest() { var x = new Shielded<DateTime>(DateTime.UtcNow); try { Shield.InTransaction(() => { Shield.SideEffect(() => { Assert.Fail("Suicide transaction has committed."); }, () => { throw new IgnoreMe(); }); // in case Assign() becomes commutative, we use Modify() to ensure conflict. x.Modify((ref DateTime d) => d = DateTime.UtcNow); var t = new Thread(() => Shield.InTransaction(() => x.Modify((ref DateTime d) => d = DateTime.UtcNow))); t.Start(); t.Join(); }); Assert.Fail("Suicide transaction did not throw."); } catch (AggregateException aggr) { Assert.AreEqual(1, aggr.InnerExceptions.Count); Assert.AreEqual(typeof(IgnoreMe), aggr.InnerException.GetType()); } bool commitFx = false; Shield.InTransaction(() => { Shield.SideEffect(() => { Assert.IsFalse(commitFx); commitFx = true; }); }); Assert.IsTrue(commitFx); bool outOfTransFx = false, outOfTransOnRollback = false; Shield.SideEffect(() => outOfTransFx = true, () => outOfTransOnRollback = true); Assert.IsTrue(outOfTransFx); Assert.IsFalse(outOfTransOnRollback); }
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 ConditionalTest() { var x = new Shielded <int>(); var testCounter = 0; var triggerCommits = 0; Shield.Conditional(() => { Interlocked.Increment(ref testCounter); return(x > 0 && (x & 1) == 0); }, () => { Shield.SideEffect(() => Interlocked.Increment(ref triggerCommits)); Assert.IsTrue(x > 0 && (x & 1) == 0); }); const int count = 1000; ParallelEnumerable.Repeat(1, count).ForAll(i => Shield.InTransaction(() => x.Modify((ref int n) => n++))); // one more, for the first call to Conditional()! btw, if this conditional were to // write anywhere, he might conflict, and an interlocked counter would give more due to // repetitions. so, this confirms reader progress too. Assert.AreEqual(count + 1, testCounter); // every change triggers it, but by the time it starts, another transaction might have // committed, so this is not a fixed number. Assert.Greater(triggerCommits, 0); // a conditional which does not depend on any Shielded is not allowed! int a = 5; Assert.Throws <InvalidOperationException>(() => Shield.Conditional(() => a > 10, () => { })); bool firstTime = true; var x2 = new Shielded <int>(); // this one succeeds in registering, but fails as soon as it gets triggered, due to changing it's // test's access pattern to an empty set. Shield.Conditional(() => { if (firstTime) { firstTime = false; return(x2 == 0); } else { // this is of course invalid, and when reaching here we have not touched any Shielded obj. return(true); } }, () => { }); try { // this will trigger the conditional Shield.InTransaction(() => x2.Modify((ref int n) => n++)); Assert.Fail(); } catch (AggregateException aggr) { Assert.AreEqual(1, aggr.InnerExceptions.Count); Assert.AreEqual(typeof(InvalidOperationException), aggr.InnerException.GetType()); } }
static void OneTransaction() { Shielded<int> sh = new Shielded<int>(); Shield.InTransaction(() => { int x = sh; Console.WriteLine("Value: {0}", x); sh.Modify((ref int a) => a = x + 1); Console.WriteLine("Value after increment: {0}", sh.Value); }); }
public void Prioritization() { var x = new Shielded <int>(); var barrier = new Barrier(2); int slowThread1Repeats = 0; var slowThread1 = new Thread(() => { barrier.SignalAndWait(); Shield.InTransaction(() => { Interlocked.Increment(ref slowThread1Repeats); int a = x; Thread.Sleep(100); x.Value = a - 1; }); }); slowThread1.Start(); IDisposable conditional = null; conditional = Shield.Conditional(() => { int i = x; return(true); }, () => { barrier.SignalAndWait(); Thread.Yield(); conditional.Dispose(); }); foreach (int i in Enumerable.Range(1, 1000)) { Shield.InTransaction(() => { x.Modify((ref int a) => a++); }); } slowThread1.Join(); Assert.Greater(slowThread1Repeats, 1); Assert.AreEqual(999, x); // now, we introduce prioritization. // this condition gets triggered before any attempt to write into x int ownerThreadId = -1; Shield.PreCommit(() => { int a = x; return(true); }, () => { var threadId = ownerThreadId; if (threadId > -1 && threadId != Thread.CurrentThread.ManagedThreadId) { // we'll cause lower prio threads to busy wait. we could also // add, e.g., an onRollback SideEffect which would wait for // a certain signal before continuing the next iteration.. // (NB that Shield.SideEffect would, of course, have to be called // before calling Rollback.) Shield.Rollback(); } }); // this will pass due to ownerThreadId == -1 Shield.InTransaction(() => x.Value = 0); int slowThread2Repeats = 0; var slowThread2 = new Thread(() => { try { barrier.SignalAndWait(); Interlocked.Exchange(ref ownerThreadId, Thread.CurrentThread.ManagedThreadId); Shield.InTransaction(() => { Interlocked.Increment(ref slowThread2Repeats); int a = x; Thread.Sleep(100); x.Value = a - 1; }); } finally { Interlocked.Exchange(ref ownerThreadId, -1); } }); slowThread2.Start(); conditional = Shield.Conditional(() => { int i = x; return(true); }, () => { barrier.SignalAndWait(); conditional.Dispose(); }); foreach (int i in Enumerable.Range(1, 1000)) { Shield.InTransaction(() => { x.Modify((ref int a) => a++); }); } slowThread2.Join(); Assert.AreEqual(1, slowThread2Repeats); Assert.AreEqual(999, x); }