Ejemplo n.º 1
0
        public void With_no_param_should_set_default_config()
        {
            var sut = new ThrottleConfiguration();

            Assert.AreEqual(20, sut.MessagePerTime);
            Assert.AreEqual(15, sut.IntervalInMinutes);
        }
Ejemplo n.º 2
0
        public ThrottledWorker(Throttle throttle)
        {
            Settings  = throttle.Settings;
            _throttle = throttle;

            int workerThreads;

            ThreadPool.GetMaxThreads(out workerThreads, out int completionPortThreads);

            if (Settings.MaxConcurrentThreads != null)
            {
                workerThreads = Settings.MaxConcurrentThreads.Value;
            }

            if (workerThreads <= 0)
            {
                throw new ArgumentNullException($"There must be at least 1 worker thread available.");
            }

            _cancellationTokenSource = new CancellationTokenSource();
            _queue   = new BlockingCollection <TRequest>();
            _workers = Enumerable.Range(0, workerThreads).Select(i => BackgroundWorker(_cancellationTokenSource.Token)).ToArray();

            _throttle = new Throttle(Settings);
        }
Ejemplo n.º 3
0
        public void With_param_should_configure_throttling()
        {
            var sut = new ThrottleConfiguration()
            {
                IntervalInMinutes = 50,
                MessagePerTime    = 20
            };

            Assert.AreEqual(20, sut.MessagePerTime);
            Assert.AreEqual(50, sut.IntervalInMinutes);
        }
Ejemplo n.º 4
0
        public void ThrottledWorker_CanPreventConcurrentRequests()
        {
            // no more than 2 concurrent threads.
            ThrottleConfiguration configuration = new ThrottleConfiguration
            {
                MaxConcurrentThreads = 2
            };
            Throttle        throttle        = new Throttle(configuration);
            ThrottledWorker throttledWorker = new ThrottledWorker(throttle);

            ManualResetEvent        awaiter = new ManualResetEvent(false);
            CancellationTokenSource outerCancellationSource = new CancellationTokenSource(5 * 1000); // 5 seconds

            const long requestCount = 5;

            long success = 0;

            for (int i = 0; i < requestCount; i++)
            {
                int       instance = i;
                Stopwatch sw       = new Stopwatch();
                sw.Start();
                throttledWorker.Enqueue(async() => {
                    CancellationTokenSource innerCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
                        outerCancellationSource.Token,
                        new CancellationTokenSource(5 * 1000).Token  // 5 seconds
                        );
                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Request {instance}: acquired lease (waitTime: {sw.Elapsed})");

                    sw.Restart();
                    awaiter.WaitOne();

                    await Task.Delay(1000, innerCancellationSource.Token); // do some work

                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Request {instance}: completed work (runTime: {sw.Elapsed})");

                    Assert.True(throttle.ActiveThreads <= configuration.MaxConcurrentThreads, $"Instance {instance}: Thread count exceeds max concurrent threads (actual: {throttle.ActiveThreads}, max: {configuration.MaxConcurrentThreads})");

                    long current = Interlocked.Increment(ref success);
                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Request {instance}: thread complete (runTime: {sw.Elapsed}, success: {current})");
                });
            }

            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] set.....");
            awaiter.Set();
            throttledWorker.Wait(outerCancellationSource.Token);
            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] complete.....");
            long actual = Interlocked.Read(ref success);

            Assert.Equal(requestCount, actual);
        }
Ejemplo n.º 5
0
        public void Throttle_CanWaitBetweenRequests_FromEndOfLastRequest()
        {
            // no more than 1 thread per second from end of last request
            ThrottleConfiguration configuration = new ThrottleConfiguration
            {
                WaitTimeBetweenLease = TimeSpan.FromSeconds(1),
                WaitStrategy         = ThrottleWaitStrategy.FromEndOfLastRequest
            };

            Throttle throttle = new Throttle(configuration);

            Task[] threads = new Task[5];

            ManualResetEvent awaiter = new ManualResetEvent(false);

            for (int i = 0; i < threads.Length; i++)
            {
                int instance = i;
                threads[i] = Task.Run(() => {
                    awaiter.WaitOne();
                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: thread started");

                    using (throttle.AcquireLease())
                    {
                        TimeSpan?timeSinceLastRequest = DateTimeOffset.Now - throttle.LastFinishTime;

                        Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: aquired lease (threads: {throttle.ActiveThreads}, timeSinceLastRequest: {timeSinceLastRequest})");

                        Assert.True(throttle.ActiveThreads <= configuration.MaxConcurrentThreads, $"Instance {instance}: Thread count exceeds max concurrent threads (actual: {throttle.ActiveThreads}, max: {configuration.MaxConcurrentThreads})");

                        if (timeSinceLastRequest != null)
                        {
                            Assert.True(timeSinceLastRequest >= configuration.WaitTimeBetweenLease, $"Instance {instance}: Wait time exceeds min wait time (actual: {timeSinceLastRequest}, min: {configuration.WaitTimeBetweenLease})");
                        }

                        Thread.Sleep(1000); // do some work

                        Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: completed work");
                    }

                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: released lease (threads: {throttle.ActiveThreads})");
                });
            }

            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] set.....");
            awaiter.Set();
            Task.WaitAll(threads);
        }
Ejemplo n.º 6
0
        public void Throttle_CanWaitBetweenRequests_TumblingWindow()
        {
            // No more than 2 requests per 5 seconds and only 1 concurrent thread.
            ThrottleConfiguration configuration = new ThrottleConfiguration
            {
                WaitTimeBetweenLease = TimeSpan.FromSeconds(5),
                WaitStrategy         = ThrottleWaitStrategy.TumblingWindow,
                MaxConcurrentThreads = 1,
                MaxRequests          = 2
            };

            Throttle throttle = new Throttle(configuration);

            Task[] threads = new Task[5];

            ManualResetEvent awaiter = new ManualResetEvent(false);

            for (int i = 0; i < threads.Length; i++)
            {
                int instance = i;
                threads[i] = Task.Run(() => {
                    awaiter.WaitOne();
                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: thread started");

                    using (throttle.AcquireLease())
                    {
                        TimeSpan?timeSinceLastRequest = DateTimeOffset.Now - throttle.LastFinishTime;

                        Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: acquired lease (window: {throttle.RateWindow?.DateTime:yyyy-MM-dd HH:mm:ss.fff}, threads: {throttle.ActiveThreads}, requests: {throttle.RequestCount})");

                        Assert.True(throttle.ActiveThreads <= configuration.MaxConcurrentThreads, $"Instance {instance}: Thread count exceeds max concurrent threads (actual: {throttle.ActiveThreads}, max: {configuration.MaxConcurrentThreads})");

                        Assert.True(throttle.RequestCount <= configuration.MaxRequests, $"Instance {instance}: Request count exceeds max request count (actual: {throttle.RequestCount}, max: {configuration.MaxRequests})");

                        Thread.Sleep(1000); // do some work

                        Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: completed work");
                    }

                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: released lease (threads: {throttle.ActiveThreads})");
                });
            }

            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] set.....");
            awaiter.Set();
            Task.WaitAll(threads);
        }
Ejemplo n.º 7
0
        public void ThrottledWorker_CanWaitBetweenRequests_SlidingWindow()
        {
            // no more than 2 request per 1 second
            ThrottleConfiguration configuration = new ThrottleConfiguration
            {
                WaitTimeBetweenLease = TimeSpan.FromSeconds(1),
                WaitStrategy         = ThrottleWaitStrategy.SlidingWindow,
                MaxRequests          = 2,
                //RecursionStrategy = ThrottleRecursionStrategy.AllowsRecursion
            };

            Throttle        throttle        = new Throttle(configuration);
            ThrottledWorker throttledWorker = new ThrottledWorker(throttle);
            const int       requestCount    = 10;

            ManualResetEvent awaiter = new ManualResetEvent(false);

            for (int i = 0; i < requestCount; i++)
            {
                int instance = i;
                throttledWorker.Enqueue(() => {
                    awaiter.WaitOne();
                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: thread started");

                    //using (throttle.AcquireLease())
                    {
                        TimeSpan?timeSinceLastRequest = DateTimeOffset.Now - throttle.LastFinishTime;

                        Assert.True(throttle.ActiveThreads <= configuration.MaxConcurrentThreads, $"Instance {instance}: Thread count exceeds max concurrent threads (actual: {throttle.ActiveThreads}, max: {configuration.MaxConcurrentThreads})");

                        Assert.True(throttle.RequestCount <= configuration.MaxRequests, $"Instance {instance}: Request count exceeds max request count (actual: {throttle.RequestCount}, max: {configuration.MaxRequests})");

                        Thread.Sleep(1000); // do some work

                        Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: completed work");
                    }
                });
            }

            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] set.....");
            awaiter.Set();
            throttledWorker.Wait();
        }
Ejemplo n.º 8
0
        public void ThrottledWorker_CanWaitBetweenRequests_FromStartOfLastRequest()
        {
            // no more than 1 thread per second from start of last request.
            ThrottleConfiguration configuration = new ThrottleConfiguration
            {
                WaitTimeBetweenLease = TimeSpan.FromSeconds(1),
                WaitStrategy         = ThrottleWaitStrategy.FromStartOfLastRequest
            };
            Throttle        throttle        = new Throttle(configuration);
            ThrottledWorker throttledWorker = new ThrottledWorker(throttle);
            const int       requestCount    = 5;

            ManualResetEvent awaiter = new ManualResetEvent(false);

            CancellationTokenSource outerCancellationSource = new CancellationTokenSource(12 * 1000); // 12 seconds

            DateTimeOffset?lastRequestTime = null;
            object         _lock           = new object();

            long success = 0;

            for (int i = 0; i < requestCount; i++)
            {
                int       instance = i;
                Stopwatch sw       = new Stopwatch();
                sw.Start();
                throttledWorker.Enqueue(() =>
                {
                    awaiter.WaitOne();

                    TimeSpan?timeSinceLastRequest;
                    lock (_lock)
                    {
                        timeSinceLastRequest = DateTimeOffset.Now - lastRequestTime;

                        if (lastRequestTime == null || throttle.LastRequestTime > lastRequestTime)
                        {
                            lastRequestTime = throttle.LastRequestTime;
                        }
                    }
                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: aquired lease (threads: {throttle.ActiveThreads}, timeSinceLastRequest: {timeSinceLastRequest})");

                    Assert.True(throttle.ActiveThreads <= configuration.MaxConcurrentThreads, $"Instance {instance}: Thread count exceeds max concurrent threads (actual: {throttle.ActiveThreads}, max: {configuration.MaxConcurrentThreads})");

                    if (timeSinceLastRequest != null)
                    {
                        Assert.True(timeSinceLastRequest >= configuration.WaitTimeBetweenLease, $"Instance {instance}: Wait time exceeds min wait time (actual: {timeSinceLastRequest}, min: {configuration.WaitTimeBetweenLease})");
                    }

                    Thread.Sleep(1000); // do some work

                    long current = Interlocked.Increment(ref success);
                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: completed work");
                });
            }

            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] set.....");
            awaiter.Set();
            throttledWorker.Wait();

            long actual = Interlocked.Read(ref success);

            Assert.Equal(requestCount, actual);
        }
Ejemplo n.º 9
0
        public Throttle(ThrottleConfiguration configuration)
        {
            Settings = configuration;

            if (configuration.MaxRequests != null)
            {
                if (configuration.WaitStrategy == null)
                {
                    throw new ArgumentNullException($"{nameof(configuration.WaitStrategy)} is required when {nameof(configuration.MaxRequests)} is specified.");
                }
                if (configuration.WaitTimeBetweenLease == null)
                {
                    throw new ArgumentNullException($"{nameof(configuration.WaitTimeBetweenLease)} is required when {nameof(configuration.MaxRequests)} is specified.");
                }
                if (configuration.MaxRequests <= 0)
                {
                    throw new ArgumentOutOfRangeException($"{nameof(configuration.MaxRequests)} must be greater than 0.");
                }

                if (configuration.MaxConcurrentThreads == null)
                {
                    configuration.MaxConcurrentThreads = configuration.MaxRequests;
                }
                if (configuration.MaxConcurrentThreads > configuration.MaxRequests)
                {
                    throw new ArgumentOutOfRangeException($"{nameof(configuration.MaxConcurrentThreads)} must be less than or equal to {nameof(configuration.MaxRequests)}.");
                }
            }

            if (configuration.WaitTimeBetweenLease != null || configuration.WaitStrategy != null)
            {
                if (configuration.WaitStrategy == null)
                {
                    throw new ArgumentNullException($"{nameof(configuration.WaitStrategy)} is required when {nameof(configuration.WaitTimeBetweenLease)} is specified.");
                }
                if (configuration.WaitTimeBetweenLease == null)
                {
                    throw new ArgumentNullException($"{nameof(configuration.WaitTimeBetweenLease)} is required when {nameof(configuration.WaitStrategy)} is specified.");
                }

                if (configuration.WaitTimeBetweenLease <= TimeSpan.Zero)
                {
                    throw new ArgumentOutOfRangeException($"{nameof(configuration.WaitTimeBetweenLease)} must be greater than 0.");
                }

                switch (configuration.WaitStrategy)
                {
                case ThrottleWaitStrategy.FromEndOfLastRequest:
                case ThrottleWaitStrategy.FromStartOfLastRequest:
                    configuration.MaxRequests          = 1;
                    configuration.MaxConcurrentThreads = 1;
                    break;

                case ThrottleWaitStrategy.SlidingWindow:
                case ThrottleWaitStrategy.TumblingWindow:
                    if (configuration.MaxRequests == null)
                    {
                        throw new ArgumentNullException($"{nameof(configuration.MaxRequests)} is required when {nameof(configuration.WaitStrategy)} is {configuration.WaitStrategy}.");
                    }
                    _requests = new List <long>();
                    break;
                }
            }

            if (configuration.MaxConcurrentThreads != null)
            {
                if (configuration.MaxConcurrentThreads <= 0)
                {
                    throw new ArgumentOutOfRangeException($"{nameof(configuration.MaxConcurrentThreads)} must be greater than 0.");
                }
                _semaphore = new SemaphoreSlim(configuration.MaxConcurrentThreads.Value, configuration.MaxConcurrentThreads.Value);
            }
        }
Ejemplo n.º 10
0
 public ThrottledWorker(ThrottleConfiguration configuration)
     : this(new Throttle(configuration))
 {
 }
Ejemplo n.º 11
0
        public void Throttle_CanPreventConcurrentRequests()
        {
            // no more than 2 concurrent threads.
            ThrottleConfiguration configuration = new ThrottleConfiguration
            {
                MaxConcurrentThreads = 2
            };
            Throttle throttle = new Throttle(configuration);

            Task[] threads = new Task[5];

            ManualResetEvent awaiter = new ManualResetEvent(false);

            CancellationTokenSource outerCancellationSource = new CancellationTokenSource(5 * 1000); // 5 seconds

            long success = 0;

            for (int i = 0; i < threads.Length; i++)
            {
                int       instance = i;
                Stopwatch sw       = new Stopwatch();
                sw.Start();
                threads[i] = Task.Run(async() => {
                    CancellationTokenSource innerCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
                        outerCancellationSource.Token,
                        new CancellationTokenSource(5 * 1000).Token  // 5 seconds
                        );
                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: thread activated (activateTime: {sw.Elapsed})");
                    sw.Restart();
                    awaiter.WaitOne();
                    Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: thread started (runTime: {sw.Elapsed})");

                    try
                    {
                        IDisposable lease = await throttle.AcquireLeaseAsync(innerCancellationSource.Token);
                        using (lease)
                        {
                            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: acquired lease (threads: {throttle.ActiveThreads}, runTime: {sw.Elapsed})");

                            await Task.Delay(1000, innerCancellationSource.Token); // do some work

                            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: completed work (runTime: {sw.Elapsed})");

                            Assert.True(throttle.ActiveThreads <= configuration.MaxConcurrentThreads, $"Instance {instance}: Thread count exceeds max concurrent threads (actual: {throttle.ActiveThreads}, max: {configuration.MaxConcurrentThreads})");
                        }
                        long current = Interlocked.Increment(ref success);
                        Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: thread complete (runTime: {sw.Elapsed}, success: {current})");
                    }
                    catch (Exception ex)
                    {
                        Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] Instance {instance}: thread failed (runTime: {sw.Elapsed}) {ex}");
                        throw new Exception("Operation failed", ex);
                    }
                });
            }

            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] set.....");
            awaiter.Set();
            Task.WaitAll(threads, outerCancellationSource.Token);

            Log.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] complete.....");
            long actual = Interlocked.Read(ref success);

            Assert.Equal(threads.Length, actual);
        }