Пример #1
0
        public async Task SimulatedListenLoop_ProcessedAllMessages_Sequentially(
            int numberOfMessagesToProcess,
            int concurrency)
        {
            var options = new ThrottledOptions()
            {
                MaxConcurrency = concurrency,
                Logger         = Substitute.For <ILogger>(),
                MessageMonitor = Substitute.For <IMessageMonitor>(),
                StartTimeout   = StartTimeout,
                ProcessMessagesSequentially = true,
            };

            var messageProcessingStrategy = new Throttled(options);
            var counter = new ThreadSafeCounter();

            var watch = Stopwatch.StartNew();

            var actions = BuildFakeIncomingMessages(numberOfMessagesToProcess, counter);

            await ListenLoopExecuted(actions, messageProcessingStrategy);

            watch.Stop();

            await Task.Delay(2000);

            counter.Count.ShouldBe(numberOfMessagesToProcess);
        }
Пример #2
0
        public async Task WhenATaskCompletes_AvailableWorkersIsIncremented()
        {
            // Arrange
            var options = new ThrottledOptions()
            {
                MaxConcurrency = 3,
                Logger         = _logger,
                MessageMonitor = _monitor,
                StartTimeout   = TimeSpan.FromSeconds(5),
            };

            var messageProcessingStrategy = new Throttled(options);
            var tcs = new TaskCompletionSource <object>();

            // Act
            (await messageProcessingStrategy.StartWorkerAsync(() => tcs.Task, CancellationToken.None)).ShouldBeTrue();

            // Assert
            messageProcessingStrategy.AvailableWorkers.ShouldBe(2);

            await AllowTasksToComplete(tcs);

            messageProcessingStrategy.MaxConcurrency.ShouldBe(3);
            messageProcessingStrategy.AvailableWorkers.ShouldBe(3);
        }
Пример #3
0
        public async Task AvailableWorkers_IsNeverNegative()
        {
            // Arrange
            var options = new ThrottledOptions()
            {
                MaxConcurrency = 10,
                Logger         = _logger,
                MessageMonitor = _monitor,
                StartTimeout   = TimeSpan.FromSeconds(5),
            };

            const int capacity = 10;
            var       messageProcessingStrategy = new Throttled(options);
            var       tcs = new TaskCompletionSource <object>();

            // Act
            for (int i = 0; i < capacity; i++)
            {
                (await messageProcessingStrategy.StartWorkerAsync(() => tcs.Task, CancellationToken.None)).ShouldBeTrue();
                messageProcessingStrategy.AvailableWorkers.ShouldBeGreaterThanOrEqualTo(0);
            }

            // Assert
            (await messageProcessingStrategy.StartWorkerAsync(() => tcs.Task, CancellationToken.None)).ShouldBeFalse();
            messageProcessingStrategy.AvailableWorkers.ShouldBe(0);

            await AllowTasksToComplete(tcs);
        }
Пример #4
0
 public void SetUp()
 {
     _fakeMonitor               = Substitute.For <IMessageMonitor>();
     _fakeAmazonBatchSize       = 10;
     _concurrencyLevel          = 20;
     _messageProcessingStrategy = new Throttled(_concurrencyLevel, _fakeAmazonBatchSize, _fakeMonitor);
     _actionsProcessed          = 0;
 }
Пример #5
0
        public void SimulatedListenLoop_WhenThrottlingOccurs_CallsMessageMonitor()
        {
            var actions = BuildFakeIncomingMessages(50);

            _messageProcessingStrategy = new Throttled(20, _fakeAmazonBatchSize, _fakeMonitor);

            ListenLoopExecuted(actions);

            _fakeMonitor.Received().IncrementThrottlingStatistic();
        }
Пример #6
0
        public async Task WhenATasksIsAdded_AvailableWorkersIsDecremented()
        {
            var messageProcessingStrategy = new Throttled(123, _fakeMonitor);
            var tcs = new TaskCompletionSource <object>();

            messageProcessingStrategy.StartWorker(() => tcs.Task);

            messageProcessingStrategy.AvailableWorkers.ShouldBe(122);
            await AllowTasksToComplete(tcs);
        }
Пример #7
0
        public async Task WhenATasksIsAdded_MaxWorkersIsUnaffected()
        {
            var messageProcessingStrategy = new Throttled(123, _fakeMonitor);
            var tcs = new TaskCompletionSource <object>();

            await messageProcessingStrategy.StartWorker(() => tcs.Task, CancellationToken.None);

            messageProcessingStrategy.MaxWorkers.ShouldBe(123);

            await AllowTasksToComplete(tcs);
        }
Пример #8
0
        public async Task WhenATasksIsAdded_MaxWorkersIsUnaffected()
        {
            var messageProcessingStrategy = new Throttled(123, _fakeMonitor);
            var tcs = new TaskCompletionSource <object>();

            messageProcessingStrategy.StartWorker(() => tcs.Task);

            Assert.That(messageProcessingStrategy.MaxWorkers, Is.EqualTo(123));

            await AllowTasksToComplete(tcs);
        }
Пример #9
0
        public void ChangeMaxAllowedMessagesInFlightAtRuntime_TheChangeIsApplied()
        {
            var MaxAllowedMessagesInFlight = Substitute.For <Func <int> >();

            MaxAllowedMessagesInFlight().Returns(100);
            _messageProcessingStrategy = new Throttled(MaxAllowedMessagesInFlight, 10, _fakeMonitor);

            MaxAllowedMessagesInFlight().Returns(90);

            Assert.That(_messageProcessingStrategy.BlockingThreshold, Is.EqualTo(90 - 10));
        }
Пример #10
0
        public async Task WhenATaskCompletes_AvailableWorkersIsIncremented()
        {
            var messageProcessingStrategy = new Throttled(3, _fakeMonitor);
            var tcs = new TaskCompletionSource <object>();

            await messageProcessingStrategy.StartWorker(() => tcs.Task, CancellationToken.None);

            messageProcessingStrategy.AvailableWorkers.ShouldBe(2);

            await AllowTasksToComplete(tcs);

            messageProcessingStrategy.MaxWorkers.ShouldBe(3);
            messageProcessingStrategy.AvailableWorkers.ShouldBe(3);
        }
Пример #11
0
        public async Task SimulatedListenLoop_WhenThrottlingDoesNotOccur_DoNotCallMessageMonitor(int messageCount, int capacity)
        {
            Assert.That(messageCount, Is.LessThanOrEqualTo(capacity), "To avoid throttling, message count must be not be over capacity");

            var fakeMonitor = Substitute.For <IMessageMonitor>();
            var messageProcessingStrategy = new Throttled(capacity, fakeMonitor);
            var counter = new ThreadSafeCounter();

            var actions = BuildFakeIncomingMessages(messageCount, counter);

            await ListenLoopExecuted(actions, messageProcessingStrategy);

            fakeMonitor.DidNotReceive().IncrementThrottlingStatistic();
        }
Пример #12
0
        public async Task <Stream> SecGetStreamAsync(string url)
        {
            Stream ToReturn = null;

            //Setup
            HttpClient hc        = new HttpClient();
            int        havetried = 0;

            while (ToReturn == null && havetried < MaxTryCount)
            {
                //Prepare the request
                TryUpdateStatus("Preparing request...");
                HttpRequestMessage req = PrepareHttpRequestMessage();
                req.RequestUri = new Uri(url);
                req.Method     = HttpMethod.Get;

                //Take the request delay timeout first
                TryUpdateStatus("Taking request delay...");
                await Task.Delay(RequestDelay);

                //Make the call
                TryUpdateStatus("Attempting call...");
                HttpResponseMessage resp = await hc.SendAsync(req);

                if (resp.StatusCode == HttpStatusCode.OK)
                {
                    ToReturn = await resp.Content.ReadAsStreamAsync();
                }
                else if (resp.StatusCode == HttpStatusCode.Forbidden) //Code 403 (throttled)
                {
                    if (Throttled != null)                            //Raise the throttled event
                    {
                        Throttled.Invoke();
                    }
                    TryUpdateStatus("Request was denied due to throttling. Waiting for timeout...");
                    await Task.Delay(TimeoutDelay);

                    havetried = havetried + 1;
                    TryUpdateStatus("Try count incremented and will try again.");
                }
            }

            //If the have tried is what caused it (it is over the limit), throw an exception
            if (havetried >= MaxTryCount)
            {
                throw new Exception("Unable to get data for URL '" + url + "'. Surpassed maximum try count of " + MaxTryCount.ToString());
            }

            return(ToReturn);
        }
Пример #13
0
        public async Task WhenATaskCompletes_AvailableWorkersIsIncremented()
        {
            var messageProcessingStrategy = new Throttled(3, _fakeMonitor);
            var tcs = new TaskCompletionSource <object>();

            messageProcessingStrategy.StartWorker(() => tcs.Task);

            Assert.That(messageProcessingStrategy.AvailableWorkers, Is.EqualTo(2));

            await AllowTasksToComplete(tcs);

            Assert.That(messageProcessingStrategy.MaxWorkers, Is.EqualTo(3));
            Assert.That(messageProcessingStrategy.AvailableWorkers, Is.EqualTo(3));
        }
Пример #14
0
        public async Task AvailableWorkers_CanReachZero()
        {
            const int capacity = 10;
            var       messageProcessingStrategy = new Throttled(capacity, _fakeMonitor);
            var       tcs = new TaskCompletionSource <object>();

            for (int i = 0; i < capacity; i++)
            {
                messageProcessingStrategy.StartWorker(() => tcs.Task);
            }

            messageProcessingStrategy.MaxWorkers.ShouldBe(capacity);
            messageProcessingStrategy.AvailableWorkers.ShouldBe(0);
            await AllowTasksToComplete(tcs);
        }
Пример #15
0
        public async Task SimulatedListenLoop_WhenThrottlingOccurs_CallsMessageMonitor(int messageCount, int capacity)
        {
            Assert.That(messageCount, Is.GreaterThan(capacity), "To cause throttling, message count must be over capacity");

            var fakeMonitor = Substitute.For <IMessageMonitor>();
            var messageProcessingStrategy = new Throttled(capacity, fakeMonitor);
            var counter = new ThreadSafeCounter();

            var actions = BuildFakeIncomingMessages(messageCount, counter);

            await ListenLoopExecuted(actions, messageProcessingStrategy);

            fakeMonitor.Received().IncrementThrottlingStatistic();
            fakeMonitor.Received().HandleThrottlingTime(Arg.Any <long>());
        }
Пример #16
0
        public async Task AvailableWorkers_IsNeverNegative()
        {
            const int capacity = 10;
            var       messageProcessingStrategy = new Throttled(capacity, _fakeMonitor);
            var       tcs = new TaskCompletionSource <object>();


            for (int i = 0; i < capacity; i++)
            {
                messageProcessingStrategy.StartWorker(() => tcs.Task);
                messageProcessingStrategy.AvailableWorkers.ShouldBeGreaterThanOrEqualTo(0);
            }

            await AllowTasksToComplete(tcs);
        }
Пример #17
0
        public void ChangeMaxWorkersAtRuntime_TheChangeIsApplied()
        {
            Func <int> maxAllowedMessagesInFlight = Substitute.For <Func <int> >();

            maxAllowedMessagesInFlight().Returns(100);
            var messageProcessingStrategy = new Throttled(maxAllowedMessagesInFlight, _fakeMonitor);

            Assert.That(messageProcessingStrategy.MaxWorkers, Is.EqualTo(100));
            Assert.That(messageProcessingStrategy.AvailableWorkers, Is.EqualTo(100));

            maxAllowedMessagesInFlight().Returns(90);

            Assert.That(messageProcessingStrategy.MaxWorkers, Is.EqualTo(90));
            Assert.That(messageProcessingStrategy.AvailableWorkers, Is.EqualTo(90));
        }
Пример #18
0
        public async Task ProcessMessagesSequentially_True_Processes_Messages_One_By_One()
        {
            // Arrange
            var options = new ThrottledOptions()
            {
                MaxConcurrency = 1,
                Logger         = _logger,
                MessageMonitor = _monitor,
                StartTimeout   = TimeSpan.FromSeconds(5),
                ProcessMessagesSequentially = true,
            };

            var strategy = new Throttled(options);

            int    count    = 0;
            object syncRoot = new object();

            Task DoWork()
            {
                if (Monitor.TryEnter(syncRoot))
                {
                    Interlocked.Increment(ref count);
                    Monitor.Exit(syncRoot);
                }
                else
                {
                    throw new InvalidOperationException("Failed to acquire lock as the thread was different.");
                }

                return(Task.CompletedTask);
            }

            // Act
            int loopCount = 100_000;

            Monitor.Enter(syncRoot);

            for (int i = 0; i < loopCount; i++)
            {
                await strategy.StartWorkerAsync(DoWork, CancellationToken.None);
            }

            Monitor.Exit(syncRoot);

            // Assert
            count.ShouldBe(loopCount);
        }
Пример #19
0
        public async Task Parallel_Processing_Does_Not_Exceed_Concurrency()
        {
            // Arrange
            int maxConcurrency = 10;

            var options = new ThrottledOptions()
            {
                MaxConcurrency = maxConcurrency,
                Logger         = _logger,
                MessageMonitor = _monitor,
                StartTimeout   = Timeout.InfiniteTimeSpan,
                ProcessMessagesSequentially = false,
            };

            var strategy = new Throttled(options);

            long workDone  = 0;
            int  loopCount = 1_000;
            bool allWorkDone;

            using (var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency))
            {
                async Task DoWork()
                {
                    if (!(await semaphore.WaitAsync(0)))
                    {
                        throw new InvalidOperationException("More workers are doing work than expected.");
                    }

                    Interlocked.Increment(ref workDone);
                    semaphore.Release();
                }

                // Act
                for (int i = 0; i < loopCount; i++)
                {
                    await strategy.StartWorkerAsync(DoWork, CancellationToken.None);
                }

                allWorkDone = SpinWait.SpinUntil(() => Interlocked.Read(ref workDone) >= 1000, TimeSpan.FromSeconds(10));
            }

            // Assert
            allWorkDone.ShouldBeTrue();
            workDone.ShouldBe(loopCount);
        }
Пример #20
0
        public void AvailableWorkers_StartsAtMaxConcurrency()
        {
            // Arrange
            var options = new ThrottledOptions()
            {
                MaxConcurrency = 123,
                Logger         = _logger,
                MessageMonitor = _monitor,
                StartTimeout   = TimeSpan.FromSeconds(5),
            };

            // Act
            var messageProcessingStrategy = new Throttled(options);

            // Assert
            messageProcessingStrategy.AvailableWorkers.ShouldBe(123);
        }
Пример #21
0
        public async Task AvailableWorkers_CanGoToZeroAndBackToFull()
        {
            const int capacity = 10;
            var       messageProcessingStrategy = new Throttled(capacity, _fakeMonitor);
            var       tcs = new TaskCompletionSource <object>();

            for (int i = 0; i < capacity; i++)
            {
                messageProcessingStrategy.StartWorker(() => tcs.Task);
            }

            Assert.That(messageProcessingStrategy.AvailableWorkers, Is.EqualTo(0));

            await AllowTasksToComplete(tcs);

            Assert.That(messageProcessingStrategy.AvailableWorkers, Is.EqualTo(capacity));
        }
Пример #22
0
        public async Task AvailableWorkers_CanGoToZeroAndBackToFull()
        {
            const int capacity = 10;
            var       messageProcessingStrategy = new Throttled(capacity, _fakeMonitor);
            var       tcs = new TaskCompletionSource <object>();

            for (int i = 0; i < capacity; i++)
            {
                await messageProcessingStrategy.StartWorker(() => tcs.Task, CancellationToken.None);
            }

            messageProcessingStrategy.AvailableWorkers.ShouldBe(0);

            await AllowTasksToComplete(tcs);

            messageProcessingStrategy.AvailableWorkers.ShouldBe(capacity);
        }
        public MarketStackService(IMapper mapper, IOptions <MarketStackOptions> options, HttpClient httpClient)
        {
            _mapper  = mapper;
            _options = options.Value;

            if (_options.MaxRequestsPerSecond >= 10)
            {
                _throttled = new Throttled(_options.MaxRequestsPerSecond / 10, 100);
            }
            else
            {
                _throttled = new Throttled(_options.MaxRequestsPerSecond, 1000);
            }

            _httpClient         = httpClient;
            _httpClient.Timeout = TimeSpan.FromMinutes(10);
            _apiUrl             = _options.Https ? "https://api.marketstack.com/v1" : "http://api.marketstack.com/v1";
        }
Пример #24
0
        public MarketstackService(IOptions <MarketstackOptions> options,
                                  HttpClient httpClient,
                                  ILogger <MarketstackService> logger)
        {
            _options = options.Value;
            if (_options.MaxRequestsPerSecond >= 10)
            {
                _throttled = new Throttled(_options.MaxRequestsPerSecond / 10, 100);
            }
            else
            {
                _throttled = new Throttled(_options.MaxRequestsPerSecond, 1000);
            }

            _httpClient         = httpClient;
            _httpClient.Timeout = TimeSpan.FromMinutes(10);
            _apiUrl             = options.Value.Https ? "https://api.marketstack.com/v1" : "http://api.marketstack.com/v1";
            _logger             = logger;
        }
Пример #25
0
        public async Task SimulatedListenLoop_ProcessedAllMessages(int numberOfMessagesToProcess)
        {
            var fakeMonitor = Substitute.For <IMessageMonitor>();
            var messageProcessingStrategy = new Throttled(ConcurrencyLevel, fakeMonitor);
            var counter = new ThreadSafeCounter();

            var watch = new Stopwatch();

            watch.Start();

            var actions = BuildFakeIncomingMessages(numberOfMessagesToProcess, counter);

            await ListenLoopExecuted(actions, messageProcessingStrategy);

            watch.Stop();

            await Task.Delay(2000);

            counter.Count.ShouldBe(numberOfMessagesToProcess);
        }
Пример #26
0
        public async Task SimulatedListenLoop_WhenThrottlingOccurs_CallsMessageMonitor(int messageCount, int concurrency)
        {
            messageCount.ShouldBeGreaterThan(concurrency, "To cause throttling, message count must be greater than concurrency.");

            var fakeMonitor = Substitute.For <IMessageMonitor>();

            var options = new ThrottledOptions()
            {
                MaxConcurrency = concurrency,
                Logger         = Substitute.For <ILogger>(),
                MessageMonitor = fakeMonitor,
                StartTimeout   = TimeSpan.FromTicks(1),
            };

            var messageProcessingStrategy = new Throttled(options);
            var counter = new ThreadSafeCounter();
            var tcs     = new TaskCompletionSource <bool>();

            for (int i = 0; i < concurrency; i++)
            {
                (await messageProcessingStrategy.StartWorkerAsync(
                     async() => await tcs.Task,
                     CancellationToken.None)).ShouldBeTrue();
            }

            messageProcessingStrategy.AvailableWorkers.ShouldBe(0);

            for (int i = 0; i < messageCount - concurrency; i++)
            {
                (await messageProcessingStrategy.StartWorkerAsync(() => Task.CompletedTask, CancellationToken.None)).ShouldBeFalse();
            }

            messageProcessingStrategy.AvailableWorkers.ShouldBe(0);

            tcs.SetResult(true);

            (await messageProcessingStrategy.WaitForAvailableWorkerAsync()).ShouldBeGreaterThan(0);

            fakeMonitor.Received().IncrementThrottlingStatistic();
            fakeMonitor.Received().HandleThrottlingTime(Arg.Any <TimeSpan>());
        }
Пример #27
0
        public async Task SimulatedListenLoop_WhenThrottlingDoesNotOccur_CallsMessageMonitor_Once(int messageCount, int concurrency)
        {
            var fakeMonitor = Substitute.For <IMessageMonitor>();

            var options = new ThrottledOptions()
            {
                MaxConcurrency = concurrency,
                Logger         = Substitute.For <ILogger>(),
                MessageMonitor = fakeMonitor,
                StartTimeout   = Timeout.InfiniteTimeSpan,
            };

            var messageProcessingStrategy = new Throttled(options);
            var counter = new ThreadSafeCounter();

            var actions = BuildFakeIncomingMessages(messageCount, counter);

            await ListenLoopExecuted(actions, messageProcessingStrategy);

            fakeMonitor.Received(1).IncrementThrottlingStatistic();
        }
Пример #28
0
        public async Task ProcessMessagesSequentially_False_Processes_Messages_In_Parallel()
        {
            // Arrange
            var options = new ThrottledOptions()
            {
                MaxConcurrency = 100,
                Logger         = _logger,
                MessageMonitor = _monitor,
                StartTimeout   = Timeout.InfiniteTimeSpan,
                ProcessMessagesSequentially = false,
            };

            var strategy = new Throttled(options);

            long count       = 0;
            var  threadsSeen = new ConcurrentBag <int>();

            Task DoWork()
            {
                threadsSeen.Add(Thread.CurrentThread.ManagedThreadId);
                Interlocked.Increment(ref count);

                return(Task.CompletedTask);
            }

            int loopCount = 1_000;

            // Act
            for (int i = 0; i < loopCount; i++)
            {
                await strategy.StartWorkerAsync(DoWork, CancellationToken.None);
            }

            bool allWorkDone = SpinWait.SpinUntil(() => Interlocked.Read(ref count) >= 1000, TimeSpan.FromSeconds(10));

            // Assert
            allWorkDone.ShouldBeTrue();
            count.ShouldBe(loopCount);
            threadsSeen.Distinct().Count().ShouldBeGreaterThan(1);
        }
Пример #29
0
        public async Task Run_ExecutionCounterInRange(int taskLimit, int limitingPeriodInMilliseconds, int testPeriodRounds)
        {
            Throttled throttled = new Throttled(taskLimit, limitingPeriodInMilliseconds);
            Stopwatch stopwatch = new Stopwatch();
            int       testPeriodInMilliseconds = limitingPeriodInMilliseconds * testPeriodRounds;
            int       expectedCounter          = taskLimit * testPeriodRounds;
            int       counter = 0;

            stopwatch.Start();

            while (stopwatch.Elapsed < TimeSpan.FromMilliseconds(testPeriodInMilliseconds))
            {
                await throttled.Run(() => Task.CompletedTask);

                if (stopwatch.Elapsed < TimeSpan.FromMilliseconds(testPeriodInMilliseconds))
                {
                    Interlocked.Increment(ref counter);
                }
            }

            Assert.Equal(expectedCounter, counter);
        }
Пример #30
0
        public async Task SimulatedListenLoop_WhenThrottlingDoesNotOccur_DoNotCallMessageMonitor(int messageCount, int concurrency)
        {
            messageCount.ShouldBeLessThanOrEqualTo(concurrency,
                                                   "To avoid throttling, message count must be not be greater than capacity.");

            var fakeMonitor = Substitute.For <IMessageMonitor>();

            var options = new ThrottledOptions()
            {
                MaxConcurrency = concurrency,
                Logger         = Substitute.For <ILogger>(),
                MessageMonitor = fakeMonitor,
                StartTimeout   = Timeout.InfiniteTimeSpan,
            };

            var messageProcessingStrategy = new Throttled(options);
            var counter = new ThreadSafeCounter();

            var actions = BuildFakeIncomingMessages(messageCount, counter);

            await ListenLoopExecuted(actions, messageProcessingStrategy);

            fakeMonitor.DidNotReceive().IncrementThrottlingStatistic();
        }