<commit_before>﻿using System;
using System.Threading;
using Foundatio.Caching;
using Foundatio.Extensions;
using Foundatio.Logging;

namespace Foundatio.Lock {
    public class ThrottlingLockProvider : ILockProvider {
        private readonly ICacheClient _cacheClient;
        private readonly TimeSpan _throttlingPeriod = TimeSpan.FromMinutes(15);
        private readonly int _maxHitsPerPeriod;

        public ThrottlingLockProvider(ICacheClient cacheClient, int maxHitsPerPeriod = 100, TimeSpan? throttlingPeriod = null) {
            _cacheClient = cacheClient;
            _maxHitsPerPeriod = maxHitsPerPeriod;

            if (maxHitsPerPeriod <= 0)
                throw new ArgumentException("Must be a positive number.", "maxHitsPerPeriod");
            
            if (throttlingPeriod.HasValue)
                _throttlingPeriod = throttlingPeriod.Value;
        }

        public IDisposable AcquireLock(string name, TimeSpan? lockTimeout = null, TimeSpan? acquireTimeout = null) {
            Logger.Trace().Message("AcquireLock: {0}", name).Write();
            if (!acquireTimeout.HasValue)
                acquireTimeout = TimeSpan.FromMinutes(1);

            var timeoutTime = DateTime.UtcNow.Add(acquireTimeout.Value);
            Logger.Trace().Message("Timeout time: {0}", timeoutTime.ToString("mm:ss.fff")).Write();
            bool allowLock = false;

            do {
                var now = DateTime.UtcNow;
                string cacheKey = GetCacheKey(name, now);

                try {
                    Logger.Trace().Message("Current time: {0} throttle: {1}", now.ToString("mm:ss.fff"), now.Floor(_throttlingPeriod).ToString("mm:ss.fff")).Write();
                    var hitCount = _cacheClient.Get<long?>(cacheKey) ?? 0;
                    Logger.Trace().Message("Current hit count: {0} max: {1}", hitCount, _maxHitsPerPeriod).Write();

                    if (hitCount <= _maxHitsPerPeriod - 1) {
                        hitCount = _cacheClient.Increment(cacheKey, 1, now.Ceiling(_throttlingPeriod));
                        
                        // make sure someone didn't beat us to it.
                        if (hitCount <= _maxHitsPerPeriod) {
                            allowLock = true;
                            break;
                        }

                        Logger.Trace().Message("Max hits exceeded after increment for {0}.", name).Write();
                    } else {
                        Logger.Trace().Message("Max hits exceeded for {0}.", name).Write();
                    }

                    var sleepUntil = now.Ceiling(_throttlingPeriod);
                    if (sleepUntil > timeoutTime) {
                        // next period is too far away<commit_msg>Don't exit immediately when next throttling period is too far away.<commit_after>﻿using System;
using System.Threading;
using Foundatio.Caching;
using Foundatio.Extensions;
using Foundatio.Logging;

namespace Foundatio.Lock {
    public class ThrottlingLockProvider : ILockProvider {
        private readonly ICacheClient _cacheClient;
        private readonly TimeSpan _throttlingPeriod = TimeSpan.FromMinutes(15);
        private readonly int _maxHitsPerPeriod;

        public ThrottlingLockProvider(ICacheClient cacheClient, int maxHitsPerPeriod = 100, TimeSpan? throttlingPeriod = null) {
            _cacheClient = cacheClient;
            _maxHitsPerPeriod = maxHitsPerPeriod;

            if (maxHitsPerPeriod <= 0)
                throw new ArgumentException("Must be a positive number.", "maxHitsPerPeriod");
            
            if (throttlingPeriod.HasValue)
                _throttlingPeriod = throttlingPeriod.Value;
        }

        public IDisposable AcquireLock(string name, TimeSpan? lockTimeout = null, TimeSpan? acquireTimeout = null) {
            Logger.Trace().Message("AcquireLock: {0}", name).Write();
            if (!acquireTimeout.HasValue)
                acquireTimeout = TimeSpan.FromMinutes(1);

            var timeoutTime = DateTime.UtcNow.Add(acquireTimeout.Value);
            Logger.Trace().Message("Timeout time: {0}", timeoutTime.ToString("mm:ss.fff")).Write();
            bool allowLock = false;

            do {
                var now = DateTime.UtcNow;
                string cacheKey = GetCacheKey(name, now);

                try {
                    Logger.Trace().Message("Current time: {0} throttle: {1}", now.ToString("mm:ss.fff"), now.Floor(_throttlingPeriod).ToString("mm:ss.fff")).Write();
                    var hitCount = _cacheClient.Get<long?>(cacheKey) ?? 0;
                    Logger.Trace().Message("Current hit count: {0} max: {1}", hitCount, _maxHitsPerPeriod).Write();

                    if (hitCount <= _maxHitsPerPeriod - 1) {
                        hitCount = _cacheClient.Increment(cacheKey, 1, now.Ceiling(_throttlingPeriod));
                        
                        // make sure someone didn't beat us to it.
                        if (hitCount <= _maxHitsPerPeriod) {
                            allowLock = true;
                            break;
                        }

                        Logger.Trace().Message("Max hits exceeded after increment for {0}.", name).Write();
                    } else {
                        Logger.Trace().Message("Max hits exceeded for {0}.", name).Write();
                    }

                    var sleepUntil = now.Ceiling(_throttlingPeriod);
                    if (sleepUntil > timeoutTime) {
                        // next period is too far away
                        Thread.Sleep(DateTime.UtcNow - timeoutTime);
{'fraction_non_alphanumeric': 0.0991617620831104, 'fraction_numerical': 0.006420545746388443, 'mean_word_length': 1.8818088386433711, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7575385', 'n_tokens_mistral': 1470, 'n_tokens_neox': 1375, 'n_words': 333}