public void When_a_catchup_has_been_waiting_for_several_poll_intervals_it_only_runs_once() { // arrange int numberOfEventsToWrite = Any.Int(10, 20); Events.Write(numberOfEventsToWrite); var testScheduler = new TestScheduler(); var projector1 = new Projector <IEvent>(() => new ReadModels1DbContext()) { OnUpdate = (work, e) => { // create some delay so that catchup2 will attempt to poll multiple times testScheduler.Sleep(1000); } }; var projector2 = new Projector <IEvent>(() => new ReadModels1DbContext()); var catchup2StatusReports = new List <ReadModelCatchupStatus>(); using (var catchup1 = CreateReadModelCatchup <ReadModels1DbContext>(projector1)) using (var catchup2 = CreateReadModelCatchup <ReadModels1DbContext>(projector2)) { bool catchup1Disposed = false; catchup1.Progress.ForEachAsync(s => { Console.WriteLine("catchup1: " + s); // when the batch is done, dispose, which should allow catchup2 to try if (s.IsEndOfBatch) { Console.WriteLine("disposing catchup1"); catchup1.Dispose(); catchup1Disposed = true; } }); catchup2.Progress.ForEachAsync(s => { catchup2StatusReports.Add(s); Console.WriteLine("catchup2: " + s); }); // act var scheduler1 = new SchedulerWatcher(testScheduler, "scheduler1"); catchup1.PollEventStore(TimeSpan.FromSeconds(1), scheduler1); var scheduler2 = new SchedulerWatcher(testScheduler, "scheduler2"); scheduler2.Schedule(TimeSpan.FromSeconds(1.5), () => { Console.WriteLine("catchup2 polling starting"); // use a higher poll frequency so the poll timer fires many times while catchup1 is running catchup2.PollEventStore(TimeSpan.FromSeconds(.5), scheduler2); }); while (!catchup1Disposed) { testScheduler.AdvanceBy(TimeSpan.FromSeconds(.1).Ticks); } testScheduler.AdvanceBy(TimeSpan.FromSeconds(1).Ticks); } // assert catchup2StatusReports.Count(s => s.IsStartOfBatch) .Should() .Be(1); }
public void When_one_concurrent_catchup_instance_terminates_due_to_eventstore_connection_loss_then_another_tries_to_take_over_immediately() { // arrange var numberOfEventsToWrite = 100; Events.Write(numberOfEventsToWrite); var projector1 = new Projector <IEvent>(() => new ReadModels1DbContext()); var projector2 = new Projector <IEvent>(() => new ReadModels1DbContext()); var catchup1StatusReports = new List <ReadModelCatchupStatus>(); var catchup2StatusReports = new List <ReadModelCatchupStatus>(); DbConnection dbConnection1 = null; using (var catchup1 = CreateReadModelCatchup <ReadModels1DbContext>(() => { var eventStoreDbContext = EventStoreDbContext(); dbConnection1 = ((IObjectContextAdapter)eventStoreDbContext).ObjectContext.Connection; return(eventStoreDbContext); }, projectors: projector1)) using (var catchup2 = CreateReadModelCatchup <ReadModels1DbContext>(projector2)) { catchup1.Progress.ForEachAsync(s => { catchup1StatusReports.Add(s); Console.WriteLine("catchup1: " + s); // when we've processed one event, cancel this catchup dbConnection1.Close(); }); catchup2.Progress.ForEachAsync(s => { catchup2StatusReports.Add(s); Console.WriteLine("catchup2: " + s); }); // act catchup1.PollEventStore(TimeSpan.FromSeconds(10), new NewThreadScheduler()); new NewThreadScheduler().Schedule(TimeSpan.FromSeconds(5), () => { Console.WriteLine("scheduling catchup2 polling"); catchup2.PollEventStore(TimeSpan.FromSeconds(10), new NewThreadScheduler()); }); var waitingOnEventId = HighestEventId + numberOfEventsToWrite; Console.WriteLine($"waiting on event id {waitingOnEventId} to be processed"); catchup1.Progress .Merge(catchup2.Progress) .FirstAsync(s => s.CurrentEventId == waitingOnEventId) .Timeout(DefaultTimeout) .Wait(); } // assert catchup1StatusReports.Count(s => !s.IsStartOfBatch) .Should() .Be(1, "sanity check that catchup1 polled"); catchup2StatusReports.Count(s => !s.IsStartOfBatch) .Should() .Be(numberOfEventsToWrite - 1); catchup2StatusReports.Count(s => s.IsStartOfBatch) .Should() .Be(1, "sanity check that catchup2 polled"); var expected = Enumerable.Range((int)HighestEventId + 1, numberOfEventsToWrite); Console.WriteLine("expected: " + expected.ToLogString()); var processed = catchup1StatusReports.Concat(catchup2StatusReports).Where(s => !s.IsStartOfBatch).Select(s => s.CurrentEventId).ToArray(); Console.WriteLine("actual: " + processed.ToLogString()); processed.ShouldBeEquivalentTo(expected); }
public void When_one_concurrent_catchup_instance_terminates_due_to_deliberate_disposal_then_another_tries_to_take_over_immediately() { // arrange int numberOfEventsToWrite = 15; Events.Write(numberOfEventsToWrite); var scheduler = new TestScheduler(); var projector1 = new Projector <IEvent>(() => new ReadModels1DbContext()); var projector2 = new Projector <IEvent>(() => new ReadModels1DbContext()); var catchup1StatusReports = new List <ReadModelCatchupStatus>(); var catchup2StatusReports = new List <ReadModelCatchupStatus>(); using (var catchup1 = CreateReadModelCatchup <ReadModels1DbContext>(projector1)) using (var catchup2 = CreateReadModelCatchup <ReadModels1DbContext>(projector2)) { catchup1.Progress.ForEachAsync(s => { catchup1StatusReports.Add(s); Console.WriteLine("catchup1: " + s); // when we've processed at least one event, cancel this catchup if (!s.IsStartOfBatch && s.NumberOfEventsProcessed > 0) { catchup1.Dispose(); } }); catchup2.Progress.ForEachAsync(s => { catchup2StatusReports.Add(s); Console.WriteLine("catchup2: " + s); }); // act catchup1.PollEventStore(TimeSpan.FromSeconds(3), scheduler); scheduler.Schedule(TimeSpan.FromSeconds(.5), () => { Console.WriteLine("scheduling catchup2 polling"); catchup2.PollEventStore(TimeSpan.FromSeconds(3), scheduler); }); scheduler.AdvanceBy(TimeSpan.FromSeconds(3.5).Ticks); } // assert catchup1StatusReports.Count(s => !s.IsStartOfBatch) .Should() .Be(1, "sanity check that catchup1 polled"); catchup2StatusReports.Count(s => !s.IsStartOfBatch) .Should() .Be(numberOfEventsToWrite - 1); catchup2StatusReports.Count(s => s.IsStartOfBatch) .Should() .Be(1, "sanity check that catchup2 polled"); var expected = Enumerable.Range((int)HighestEventId + 1, numberOfEventsToWrite); Console.WriteLine("expected: " + expected.ToLogString()); var processed = catchup1StatusReports.Concat(catchup2StatusReports).Where(s => !s.IsStartOfBatch).Select(s => s.CurrentEventId).ToArray(); Console.WriteLine("actual: " + processed.ToLogString()); processed.ShouldBeEquivalentTo(expected); }
public void Events_committed_to_the_event_store_are_caught_up_by_multiple_independent_read_model_stores() { var productName = Any.Paragraph(4); var projector1 = new Projector <Order.ItemAdded>(() => new ReadModels1DbContext()) { OnUpdate = (work, e) => new ReadModels1DbContext().DisposeAfter(db => UpdateReservedInventory(db, e)) }; var projector2 = new Projector <Order.ItemAdded>(() => new ReadModels2DbContext()) { OnUpdate = (work, e) => new ReadModels2DbContext().DisposeAfter(db => UpdateReservedInventory(db, e)) }; var numberOfEvents = Any.Int(10, 50); using (var disposables = new CompositeDisposable()) using (var catchup1 = CreateReadModelCatchup <ReadModels1DbContext>(projector1)) using (var catchup2 = CreateReadModelCatchup <ReadModels2DbContext>(projector2)) { catchup1.Progress.ForEachAsync(p => Console.WriteLine("catchup1: " + p)); catchup2.Progress.ForEachAsync(p => Console.WriteLine("catchup2: " + p)); Action <string, ThreadStart> startThread = (name, start) => { var thread = new Thread(() => { Console.WriteLine($"starting thread ({Thread.CurrentThread.ManagedThreadId})"); start(); Console.WriteLine($"ended thread ({Thread.CurrentThread.ManagedThreadId})"); }); thread.Name = name; thread.Start(); disposables.Add(Disposable.Create(thread.Abort)); }; Events.Write(numberOfEvents, i => new Order.ItemAdded { ProductName = productName, Quantity = 1, AggregateId = Any.Guid() }); startThread("catchup1", () => { catchup1.Run().TimeoutAfter(DefaultTimeout).Wait(); catchup1.Dispose(); }); startThread("catchup2", () => { catchup2.Run().TimeoutAfter(DefaultTimeout).Wait(); catchup2.Dispose(); }); Console.WriteLine("Waiting on catchups to complete"); // wait on both catchups to complete catchup1 .Progress .Timeout(DefaultTimeout) .Merge(catchup2.Progress .Timeout(DefaultTimeout)) .Where(p => p.IsEndOfBatch) .Take(2) .Timeout(DefaultTimeout) .Wait(); } Action <DbContext> verify = db => { var readModelInfoName = ReadModelInfo.NameForProjector(projector1); var readModelInfos = db.Set <ReadModelInfo>(); Console.WriteLine(new { readModelInfos }.ToLogString()); readModelInfos .Single(i => i.Name == readModelInfoName) .CurrentAsOfEventId .Should() .Be(HighestEventId + numberOfEvents); var productInventories = db.Set <ProductInventory>(); Console.WriteLine(new { productInventories }.ToLogString()); productInventories .Single(pi => pi.ProductName == productName) .QuantityReserved .Should() .Be(numberOfEvents); }; Console.WriteLine("verifying ReadModels1DbContext..."); new ReadModels1DbContext().DisposeAfter(r => verify(r)); Console.WriteLine("verifying ReadModels2DbContext..."); new ReadModels2DbContext().DisposeAfter(r => verify(r)); }
private IUpdateProjectionWhen <T> CreateProjector <T>( Action <T> action = null) where T : IEvent => Projector.Create(action ?? (_ => { })).Named(Any.CamelCaseName(6));