public void QueueSemantics()
        {
            var queue = new SlidingWindowQueue();

            queue.Add(new TransactionData(-1, 0, this.Data));
            Assert.ThrowsAny <ArgumentException>(() => queue.Add(new TransactionData(-1, 0, this.Data)));
            Assert.ThrowsAny <ArgumentException>(() => queue.Add(new TransactionData(-2, -1, this.Data)));
            queue.Add(new TransactionData(0, 1, this.Data));
            Assert.ThrowsAny <ArgumentException>(() => queue.Add(new TransactionData(-1, 0, this.Data)));
        }
        private SlidingWindowQueue GenerateQueueWithData(int numTransactions)
        {
            var queue = new SlidingWindowQueue();

            for (int i = 0; i < numTransactions; ++i)
            {
                queue.Add(new TransactionData(i - 1, i, this.Data));
            }
            return(queue);
        }
        // case 6
        public void OneAddAndSlideTaskConcurrently()
        {
            var       queue = new SlidingWindowQueue();
            long      numTotalTransactionsAdded   = 0;
            long      numTotalTransactionsRemoved = 0;
            const int MaxTimeToWaitInMs           = 1000 * 20; // 20 seconds of test

            var addTask = Task.Run(async() => {
                var rand  = new Random();
                var watch = Stopwatch.StartNew();
                while (watch.ElapsedMilliseconds < MaxTimeToWaitInMs)
                {
                    long numTransactionsAddedLocal = Interlocked.Read(ref numTotalTransactionsAdded);
                    int transactionsAdded          = rand.Next(100, 1000);
                    for (int i = 1; i <= transactionsAdded; ++i)
                    {
                        var lsn = numTransactionsAddedLocal + i;
                        queue.Add(new TransactionData(lsn - 1, lsn, this.Data));
                        Interlocked.Increment(ref numTotalTransactionsAdded);
                    }

                    // Console.WriteLine($"Added total : {numTransactionsAddedLocal + transactionsAdded}");
                    await Task.Delay(rand.Next(0, 10));
                }
            });

            var slideTask = Task.Run(async() => {
                var rand  = new Random();
                var watch = Stopwatch.StartNew();
                while (watch.ElapsedMilliseconds < MaxTimeToWaitInMs)
                {
                    if (addTask.IsCompleted)
                    {
                        break;
                    }

                    long numTransactionsAddedLocal = Interlocked.Read(ref numTotalTransactionsAdded);
                    var transactions             = queue.GetTransactions(numTransactionsAddedLocal);
                    int numRemovedTransactions   = queue.SlideWindowTill(numTransactionsAddedLocal);
                    numTotalTransactionsRemoved += numRemovedTransactions;

                    // Console.WriteLine($"Removed total : {numTotalTransactionsRemoved}");
                    Assert.True(transactions.Count == numRemovedTransactions,
                                $"{transactions.Count} == {numRemovedTransactions} : " +
                                $"No more transactions can be added with lsn less than {numTransactionsAddedLocal}");

                    long lastTransactionLsn = long.MinValue;
                    // all transactions are received in monotonically increasing lsn.
                    foreach (var transaction in transactions)
                    {
                        Assert.True(transaction.Lsn >= lastTransactionLsn);
                        lastTransactionLsn = transaction.Lsn;
                        Assert.Equal(this.Data, transaction.Data);
                    }

                    await Task.Delay(rand.Next(0, 10));
                }
            });

            Task.WaitAll(new Task[] { addTask, slideTask });
            Console.WriteLine($"Added : {numTotalTransactionsAdded} Removed: {numTotalTransactionsRemoved}");
            Assert.True(numTotalTransactionsAdded >= numTotalTransactionsRemoved, "Not possible : Removed more transactions than added.");
            Assert.True(queue.SlideWindowTill(numTotalTransactionsAdded + 1) == (numTotalTransactionsAdded - numTotalTransactionsRemoved));
        }
        // We don't support multiple producer or multiple consumer.
        public void OneProducerMultiConsumerFail()
        {
            var       queue = new SlidingWindowQueue();
            long      numTotalTransactionsAdded   = 0;
            long      numTotalTransactionsRemoved = 0;
            const int MaxTimeToWaitInMs           = 1000 * 20; // 20 seconds of test
            var       rand = new Random();

            Func <Task> runProducer = async() => {
                var watch = Stopwatch.StartNew();
                while (watch.ElapsedMilliseconds < MaxTimeToWaitInMs)
                {
                    var numTransactions   = Interlocked.Read(ref numTotalTransactionsAdded);
                    int transactionsToAdd = rand.Next(100, 1000);
                    for (int i = 1; i <= transactionsToAdd; ++i)
                    {
                        var lsn = numTransactions + i;
                        queue.Add(new TransactionData(lsn - 1, lsn, this.Data));
                    }
                    Interlocked.Exchange(ref numTotalTransactionsAdded, numTransactions + transactionsToAdd);
                    await Task.Delay(rand.Next(0, 2));
                }
            };

            Func <int, Task> runConsumer = async(int index) => {
                var watch = Stopwatch.StartNew();
                while (watch.ElapsedMilliseconds < MaxTimeToWaitInMs)
                {
                    var numTransactions = Interlocked.Read(ref numTotalTransactionsAdded);
                    var transactions    = queue.GetTransactions(numTransactions);
                    var numRemoved      = queue.SlideWindowTill(numTransactions);
                    Assert.True(transactions.Count >= numRemoved);
                    while (true)
                    {
                        var totalRemoved = Interlocked.Read(ref numTotalTransactionsRemoved);
                        if (totalRemoved == Interlocked.CompareExchange(ref numTotalTransactionsRemoved, totalRemoved + numRemoved, totalRemoved))
                        {
                            break;
                        }
                    }
                    await Task.Delay(rand.Next(0, 10));
                }
            };

            const int numConsumers = 10;
            var       allTasks     = new List <Task>(1 + numConsumers);

            allTasks.Add(runProducer());
            for (int i = 0; i < numConsumers; ++i)
            {
                allTasks.Add(runConsumer(i));
            }

            try
            {
                Task.WaitAll(allTasks.ToArray());
            }
            catch (AggregateException ex)
            {
                foreach (var e in ex.Flatten().InnerExceptions)
                {
                    if (!e.Message.Contains("SlidingWindowQueue :"))
                    {
                        throw;
                    }
                }
            }
        }