public When_a_commit_is_persisted()
            {
                Given(() =>
                {
                    UseThe(new TransactionBuilder().WithCheckpoint(123).Build());

                    var eventStore = A.Fake <IPassiveEventStore>();
                    A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).Returns(new[] { The <Transaction>() });

                    var adapter = new PollingEventStoreAdapter(eventStore, 11, pollingInterval, 100, () => DateTime.UtcNow);

                    WithSubject(_ => adapter.Subscribe);
                });

                When(() =>
                {
                    Subject(null, new Subscriber
                    {
                        HandleTransactions = (transactions, info) =>
                        {
                            transactionHandledSource.SetResult(transactions.First());

                            return(Task.FromResult(0));
                        }
                    }, "someId");
                });
            }
            public When_a_commit_is_already_projected()
            {
                Given(() =>
                {
                    Transaction projectedCommit   = new TransactionBuilder().WithCheckpoint(123).Build();
                    Transaction unprojectedCommit = new TransactionBuilder().WithCheckpoint(124).Build();

                    var eventStore = A.Fake <IPassiveEventStore>();
                    A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).Returns(new[] { projectedCommit, unprojectedCommit });
                    A.CallTo(() => eventStore.GetFrom(123)).Returns(new[] { unprojectedCommit });

                    var adapter = new PollingEventStoreAdapter(eventStore, 11, 1.Seconds(), 100, () => DateTime.UtcNow);
                    WithSubject(_ => adapter.Subscribe);
                });

                When(() =>
                {
                    Subject(123, new Subscriber
                    {
                        HandleTransactions = (transactions, info) =>
                        {
                            transactionHandledSource.SetResult(transactions.First());

                            return(Task.FromResult(0));
                        }
                    }, "someId");
                });
            }
            public When_the_persistency_engine_is_temporarily_unavailable()
            {
                Given(() =>
                {
                    UseThe(new TransactionBuilder().WithCheckpoint(123).Build());

                    var eventStore = A.Fake <IPassiveEventStore>();
                    A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).Returns(new[] { The <Transaction>() });
                    A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).Throws(new ApplicationException()).Once();

                    var adapter = new PollingEventStoreAdapter(eventStore, 11, pollingInterval, 100, () => DateTime.UtcNow);

                    WithSubject(_ => adapter.Subscribe);
                });

                When(() =>
                {
                    Subject(null, new Subscriber
                    {
                        HandleTransactions = (transactions, info) =>
                        {
                            actualTransaction = transactions.First();
                            return(Task.FromResult(0));
                        }
                    }, "someId");
                });
            }
            public When_requesting_a_subscription_beyond_the_highest_available_checkpoint()
            {
                Given(() =>
                {
                    var eventStore = A.Fake <IPassiveEventStore>();
                    A.CallTo(() => eventStore.GetFrom(999)).Returns(new Transaction[0]);

                    var adapter = new PollingEventStoreAdapter(eventStore, 11, 1.Seconds(), 100, () => DateTime.UtcNow);

                    WithSubject(_ => adapter.Subscribe);
                });

                When(() =>
                {
                    Subject(1000, new Subscriber
                    {
                        HandleTransactions = (transactions, info) =>
                        {
                            throw new InvalidOperationException("The adapter should not provide any events.");
                        },
                        NoSuchCheckpoint = info =>
                        {
                            noSuchCheckpointRaised.SetResult(true);

                            return(Task.FromResult(0));
                        }
                    }, "myIde");
                });
            }
            When_a_subscription_starts_after_zero_checkpoint_and_another_subscription_starts_after_null_checkpoint_while_the_first_subscription_is_loading_the_first_page_from_the_event_store()
            {
                Given(() =>
                {
                    IPassiveEventStore eventStore = A.Fake <IPassiveEventStore>();
                    A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).ReturnsLazily(call =>
                    {
                        long checkpoint = call.GetArgument <long?>(0) ?? 0;

                        aSubscriptionStartedLoading.Set();

                        if (!secondSubscriptionCreated.Wait(TimeSpan.FromSeconds(10)))
                        {
                            throw new InvalidOperationException("The second subscription has not been created in 10 seconds.");
                        }

                        // Give the second subscription enough time to access the cache.
                        Thread.Sleep(TimeSpan.FromSeconds(1));

                        return(checkpoint > 0
                            ? new Transaction[0]
                            : new[] { new TransactionBuilder().WithCheckpoint(1).Build() });
                    });

                    var adapter = new PollingEventStoreAdapter(eventStore, 11, pollingInterval, 100, () => DateTime.UtcNow);
                    WithSubject(_ => adapter.Subscribe);
                });

                When(() =>
                {
                    Subject(0, new Subscriber
                    {
                        HandleTransactions = (transactions, info) => Task.FromResult(0)
                    }, "firstId");

                    if (!aSubscriptionStartedLoading.Wait(TimeSpan.FromSeconds(10)))
                    {
                        throw new InvalidOperationException("The first subscription has not started loading in 10 seconds.");
                    }

                    Subject(null, new Subscriber
                    {
                        HandleTransactions = (transactions, info) =>
                        {
                            secondSubscriptionReceivedTheTransaction.Set();
                            return(Task.FromResult(0));
                        }
                    }, "secondId");

                    secondSubscriptionCreated.Set();
                });
            }
            public When_there_are_no_more_commits()
            {
                Given(() =>
                {
                    var eventStore = A.Fake <IPassiveEventStore>();

                    A.CallTo(() => eventStore.GetFrom(A <long?> .Ignored)).ReturnsLazily <IEnumerable <Transaction>, long?>(
                        checkpoint =>
                    {
                        pollingTimeStamps.Add(new PollingCall(checkpoint, DateTime.UtcNow));
                        if (pollingTimeStamps.Count == 4)
                        {
                            pollingCompleted.SetResult(true);
                        }

                        long checkPoint = checkpoint ?? 0;
                        long offsetToDetectAheadSubscriber = 1;

                        if (checkPoint <= (subscriptionCheckpoint - offsetToDetectAheadSubscriber))
                        {
                            return(new TransactionBuilder().WithCheckpoint(checkPoint + 1).BuildAsEnumerable());
                        }
                        else
                        {
                            return(new Transaction[0]);
                        }
                    });

                    var adapter = new PollingEventStoreAdapter(eventStore, 0, pollingInterval, 100, () => DateTime.UtcNow);
                    WithSubject(_ => adapter.Subscribe);
                });

                When(() =>
                {
                    return(Subject(subscriptionCheckpoint, new Subscriber
                    {
                        HandleTransactions = (transactions, info) => Task.FromResult(0)
                    }, "someId"));
                });
            }
        static void Main(string[] args)
        {
            var stopWatch = new Stopwatch();

            stopWatch.Start();

//            var adapter = new PollingEventStoreAdapter(new PassiveEventStore(), 50000, 5.Seconds(), 1000, () => DateTime.UtcNow, messageFunc => Console.WriteLine(messageFunc()));
            var adapter = new PollingEventStoreAdapter(new PassiveEventStore(), 50000, TimeSpan.FromSeconds(5), 1000, () => DateTime.UtcNow);

            int maxSubscribers = 50;

            for (int id = 0; id < maxSubscribers; id++)
            {
                int localId = id;
                adapter.Subscribe(0, new Subscriber
                {
                    HandleTransactions = (transactions, info) =>
                    {
                        Console.WriteLine(
                            $"{stopWatch.Elapsed}: Subscriber {info.Id} received transactions {transactions.First().Checkpoint} to {transactions.Last().Checkpoint} on thead {Thread.CurrentThread.ManagedThreadId}");

                        Thread.Sleep(new Random().Next(100, 500));

                        return(Task.FromResult(0));
                    },
                    NoSuchCheckpoint = info => Task.FromResult(0)
                }, id.ToString());

                Console.WriteLine($"{stopWatch.Elapsed}: Started subscriber {localId}");

                Thread.Sleep(1000);
            }

            Console.WriteLine("Press a key to shutdown");
            Console.ReadLine();

            adapter.Dispose();
        }
 /// <summary>
 /// Creates an adapter that observes an implementation of <see cref="IPersistStreams"/> and efficiently handles
 /// multiple subscribers.
 /// </summary>
 /// <param name="eventStore">
 /// The persistency implementation that the NEventStore is configured with.
 /// </param>
 /// <param name="cacheSize">
 /// The size of the LRU cache that will hold transactions already loaded from the event store. The larger the cache,
 /// the higher the chance multiple subscribers can reuse the same transactions without hitting the underlying event store.
 /// Set to <c>0</c> to disable the cache alltogether.
 /// </param>
 /// <param name="pollInterval">
 /// The amount of time to wait before polling again after the event store has not yielded any transactions anymore.
 /// </param>
 /// <param name="maxPageSize">
 /// The size of the page of transactions the adapter should load from the event store for every query.
 /// </param>
 /// <param name="getUtcNow">
 /// Provides the current date and time in UTC.
 /// </param>
 public NEventStoreAdapter(IPersistStreams eventStore, int cacheSize, TimeSpan pollInterval, int maxPageSize,
                           Func <DateTime> getUtcNow)
 {
     pollingAdapter = new PollingEventStoreAdapter(new StreamPersisterAdapter(eventStore), cacheSize, pollInterval, maxPageSize, getUtcNow);
 }