From a88e84a335289145e493a0f438ca489c103d7d56 Mon Sep 17 00:00:00 2001
From: Nick Cromwell <nick.cromwell@rangerlabs.io>
Date: Sat, 19 Dec 2020 14:07:23 -0500
Subject: [PATCH] Remove need for ProcessingFactory in favor of injectible
 ProcessingStrategy; remove StackExchange code and references

---
 .../AspNetCoreRateLimit.csproj                |  3 --
 .../Core/ClientRateLimitProcessor.cs          | 10 ++--
 .../Core/IRateLimitProcessor.cs               |  3 --
 .../Core/IpRateLimitProcessor.cs              | 10 ++--
 .../AsyncKeyLockProcessingStrategy.cs         | 13 ++---
 .../IProcessingStrategy.cs                    |  2 +-
 .../IProcessingStrategyFactory.cs             |  7 ---
 .../ProcessingStrategy.cs                     | 17 +++----
 .../ProcessingStrategyFactory.cs              | 27 ----------
 .../StackExchangeRedisProcessingStrategy.cs   | 44 ----------------
 .../Middleware/ClientRateLimitMiddleware.cs   |  4 +-
 .../Middleware/IpRateLimitMiddleware.cs       |  8 +--
 src/AspNetCoreRateLimit/StartupExtensions.cs  | 12 +----
 .../StackExchangeRedisClientPolicyStore.cs    | 34 -------------
 .../StackExchangeRedisIpPolicyStore.cs        | 31 ------------
 ...StackExchangeRedisRateLimitCounterStore.cs | 12 -----
 .../StackExchangeRedisRateLimitStore.cs       | 50 -------------------
 17 files changed, 31 insertions(+), 256 deletions(-)
 delete mode 100644 src/AspNetCoreRateLimit/Core/ProcessingStrategies/IProcessingStrategyFactory.cs
 delete mode 100644 src/AspNetCoreRateLimit/Core/ProcessingStrategies/ProcessingStrategyFactory.cs
 delete mode 100644 src/AspNetCoreRateLimit/Core/ProcessingStrategies/StackExchangeRedisProcessingStrategy.cs
 delete mode 100644 src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisClientPolicyStore.cs
 delete mode 100644 src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisIpPolicyStore.cs
 delete mode 100644 src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisRateLimitCounterStore.cs
 delete mode 100644 src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisRateLimitStore.cs

diff --git a/src/AspNetCoreRateLimit/AspNetCoreRateLimit.csproj b/src/AspNetCoreRateLimit/AspNetCoreRateLimit.csproj
index 580a5d12..0a2e31b3 100644
--- a/src/AspNetCoreRateLimit/AspNetCoreRateLimit.csproj
+++ b/src/AspNetCoreRateLimit/AspNetCoreRateLimit.csproj
@@ -27,7 +27,6 @@
     <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
     <PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
-    <PackageReference Include="StackExchange.Redis" Version="2.2.4" />
   </ItemGroup>
 
   <ItemGroup Condition="$(TargetFramework) == 'netcoreapp3.1'">
@@ -35,7 +34,6 @@
     <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.9" />
     <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.9" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
-    <PackageReference Include="StackExchange.Redis" Version="2.2.4" />
   </ItemGroup>
 
   <ItemGroup Condition="$(TargetFramework) == 'net5.0'">
@@ -43,7 +41,6 @@
     <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
-    <PackageReference Include="StackExchange.Redis" Version="2.2.4" />
   </ItemGroup>
 
   <ItemGroup Condition="$(TargetFramework) == 'netcoreapp3.1'">
diff --git a/src/AspNetCoreRateLimit/Core/ClientRateLimitProcessor.cs b/src/AspNetCoreRateLimit/Core/ClientRateLimitProcessor.cs
index 61f4bc43..5ff509c0 100644
--- a/src/AspNetCoreRateLimit/Core/ClientRateLimitProcessor.cs
+++ b/src/AspNetCoreRateLimit/Core/ClientRateLimitProcessor.cs
@@ -9,18 +9,20 @@ public class ClientRateLimitProcessor : RateLimitProcessor, IRateLimitProcessor
         private readonly ClientRateLimitOptions _options;
         private readonly IProcessingStrategy _processingStrategy;
         private readonly IRateLimitStore<ClientRateLimitPolicy> _policyStore;
+        private readonly ICounterKeyBuilder _counterKeyBuilder;
 
         public ClientRateLimitProcessor(
-                IProcessingStrategyFactory processingStrategyFactory,
                 ClientRateLimitOptions options,
                 IRateLimitCounterStore counterStore,
                 IClientPolicyStore policyStore,
-                IRateLimitConfiguration config)
+                IRateLimitConfiguration config,
+                IProcessingStrategy processingStrategy)
             : base(options)
         {
             _options = options;
             _policyStore = policyStore;
-            _processingStrategy = processingStrategyFactory.CreateProcessingStrategy(counterStore, new ClientCounterKeyBuilder(options), config, options);
+            _counterKeyBuilder = new ClientCounterKeyBuilder(options);
+            _processingStrategy = processingStrategy;
         }
 
         public async Task<IEnumerable<RateLimitRule>> GetMatchingRulesAsync(ClientRequestIdentity identity, CancellationToken cancellationToken = default)
@@ -32,7 +34,7 @@ public async Task<IEnumerable<RateLimitRule>> GetMatchingRulesAsync(ClientReques
 
         public async Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, CancellationToken cancellationToken = default)
         {
-            return await _processingStrategy.ProcessRequestAsync(requestIdentity, rule, cancellationToken);
+            return await _processingStrategy.ProcessRequestAsync(requestIdentity, rule, _counterKeyBuilder, _options, cancellationToken);
         }
     }
 }
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Core/IRateLimitProcessor.cs b/src/AspNetCoreRateLimit/Core/IRateLimitProcessor.cs
index abbb53fd..f87819c9 100644
--- a/src/AspNetCoreRateLimit/Core/IRateLimitProcessor.cs
+++ b/src/AspNetCoreRateLimit/Core/IRateLimitProcessor.cs
@@ -7,11 +7,8 @@ namespace AspNetCoreRateLimit
     public interface IRateLimitProcessor
     {
         Task<IEnumerable<RateLimitRule>> GetMatchingRulesAsync(ClientRequestIdentity identity, CancellationToken cancellationToken = default);
-
         RateLimitHeaders GetRateLimitHeaders(RateLimitCounter? counter, RateLimitRule rule, CancellationToken cancellationToken = default);
-
         Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, CancellationToken cancellationToken = default);
-
         bool IsWhitelisted(ClientRequestIdentity requestIdentity);
     }
 }
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Core/IpRateLimitProcessor.cs b/src/AspNetCoreRateLimit/Core/IpRateLimitProcessor.cs
index 9abbfc95..a5191ad1 100644
--- a/src/AspNetCoreRateLimit/Core/IpRateLimitProcessor.cs
+++ b/src/AspNetCoreRateLimit/Core/IpRateLimitProcessor.cs
@@ -10,18 +10,20 @@ public class IpRateLimitProcessor : RateLimitProcessor, IRateLimitProcessor
         private readonly IpRateLimitOptions _options;
         private readonly IRateLimitStore<IpRateLimitPolicies> _policyStore;
         private readonly IProcessingStrategy _processingStrategy;
+        private readonly ICounterKeyBuilder _counterKeyBuilder;
 
         public IpRateLimitProcessor(
-                IProcessingStrategyFactory processingStrategyFactory,
                 IpRateLimitOptions options,
                 IRateLimitCounterStore counterStore,
                 IIpPolicyStore policyStore,
-                IRateLimitConfiguration config)
+                IRateLimitConfiguration config,
+                IProcessingStrategy processingStrategy)
             : base(options)
         {
             _options = options;
             _policyStore = policyStore;
-            _processingStrategy = processingStrategyFactory.CreateProcessingStrategy(counterStore, new IpCounterKeyBuilder(options), config, options);
+            _counterKeyBuilder = new IpCounterKeyBuilder(options);
+            _processingStrategy = processingStrategy;
         }
 
 
@@ -47,7 +49,7 @@ public async Task<IEnumerable<RateLimitRule>> GetMatchingRulesAsync(ClientReques
 
         public async Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, CancellationToken cancellationToken = default)
         {
-            return await _processingStrategy.ProcessRequestAsync(requestIdentity, rule, cancellationToken);
+            return await _processingStrategy.ProcessRequestAsync(requestIdentity, rule, _counterKeyBuilder, _options, cancellationToken);
         }
     }
 }
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/AsyncKeyLockProcessingStrategy.cs b/src/AspNetCoreRateLimit/Core/ProcessingStrategies/AsyncKeyLockProcessingStrategy.cs
index 9cc31465..6b2b6e0d 100644
--- a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/AsyncKeyLockProcessingStrategy.cs
+++ b/src/AspNetCoreRateLimit/Core/ProcessingStrategies/AsyncKeyLockProcessingStrategy.cs
@@ -6,25 +6,20 @@ namespace AspNetCoreRateLimit
 {
     public class AsyncKeyLockProcessingStrategy : ProcessingStrategy
     {
-        private readonly RateLimitOptions _options;
         private readonly IRateLimitCounterStore _counterStore;
-        private readonly ICounterKeyBuilder _counterKeyBuilder;
         private readonly IRateLimitConfiguration _config;
 
-        public AsyncKeyLockProcessingStrategy(IRateLimitCounterStore counterStore, ICounterKeyBuilder counterKeyBuilder, IRateLimitConfiguration config, RateLimitOptions options)
-            : base(counterKeyBuilder, config, options)
+        public AsyncKeyLockProcessingStrategy(IRateLimitCounterStore counterStore, IRateLimitConfiguration config)
+            : base(config)
         {
             _counterStore = counterStore;
-            _counterKeyBuilder = counterKeyBuilder;
             _config = config;
-            _options = options;
         }
 
-
         /// The key-lock used for limiting requests.
         private static readonly AsyncKeyLock AsyncLock = new AsyncKeyLock();
 
-        public override async Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, CancellationToken cancellationToken = default)
+        public override async Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, ICounterKeyBuilder counterKeyBuilder, RateLimitOptions rateLimitOptions, CancellationToken cancellationToken = default)
         {
             var counter = new RateLimitCounter
             {
@@ -32,7 +27,7 @@ public override async Task<RateLimitCounter> ProcessRequestAsync(ClientRequestId
                 Count = 1
             };
 
-            var counterId = BuildCounterKey(requestIdentity, rule);
+            var counterId = BuildCounterKey(requestIdentity, rule, counterKeyBuilder, rateLimitOptions);
 
             // serial reads and writes on same key
             using (await AsyncLock.WriterLockAsync(counterId).ConfigureAwait(false))
diff --git a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/IProcessingStrategy.cs b/src/AspNetCoreRateLimit/Core/ProcessingStrategies/IProcessingStrategy.cs
index e15b3b2f..9dcadf24 100644
--- a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/IProcessingStrategy.cs
+++ b/src/AspNetCoreRateLimit/Core/ProcessingStrategies/IProcessingStrategy.cs
@@ -5,6 +5,6 @@ namespace AspNetCoreRateLimit
 {
     public interface IProcessingStrategy
     {
-        Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, CancellationToken cancellationToken = default);
+        Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, ICounterKeyBuilder counterKeyBuilder, RateLimitOptions rateLimitOptions, CancellationToken cancellationToken = default);
     }
 }
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/IProcessingStrategyFactory.cs b/src/AspNetCoreRateLimit/Core/ProcessingStrategies/IProcessingStrategyFactory.cs
deleted file mode 100644
index 75c506a9..00000000
--- a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/IProcessingStrategyFactory.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace AspNetCoreRateLimit
-{
-    public interface IProcessingStrategyFactory
-    {
-        ProcessingStrategy CreateProcessingStrategy(IRateLimitCounterStore counterStore, ICounterKeyBuilder counterKeyBuilder, IRateLimitConfiguration config, RateLimitOptions options);
-    }
-}
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/ProcessingStrategy.cs b/src/AspNetCoreRateLimit/Core/ProcessingStrategies/ProcessingStrategy.cs
index 2cd32efe..f366b089 100644
--- a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/ProcessingStrategy.cs
+++ b/src/AspNetCoreRateLimit/Core/ProcessingStrategies/ProcessingStrategy.cs
@@ -8,23 +8,20 @@ namespace AspNetCoreRateLimit
 {
     public abstract class ProcessingStrategy : IProcessingStrategy
     {
-        private readonly RateLimitOptions _options;
-        private readonly ICounterKeyBuilder _counterKeyBuilder;
         private readonly IRateLimitConfiguration _config;
 
-        public ProcessingStrategy(ICounterKeyBuilder counterKeyBuilder, IRateLimitConfiguration config, RateLimitOptions options)
-            : base()
+        protected ProcessingStrategy(IRateLimitConfiguration config)
         {
-            _counterKeyBuilder = counterKeyBuilder;
             _config = config;
-            _options = options;
         }
 
-        protected virtual string BuildCounterKey(ClientRequestIdentity requestIdentity, RateLimitRule rule)
+        public abstract Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, ICounterKeyBuilder counterKeyBuilder, RateLimitOptions rateLimitOptions, CancellationToken cancellationToken = default);
+
+        protected virtual string BuildCounterKey(ClientRequestIdentity requestIdentity, RateLimitRule rule, ICounterKeyBuilder counterKeyBuilder, RateLimitOptions rateLimitOptions)
         {
-            var key = _counterKeyBuilder.Build(requestIdentity, rule);
+            var key = counterKeyBuilder.Build(requestIdentity, rule);
 
-            if (_options.EnableEndpointRateLimiting && _config.EndpointCounterKeyBuilder != null)
+            if (rateLimitOptions.EnableEndpointRateLimiting && _config.EndpointCounterKeyBuilder != null)
             {
                 key += _config.EndpointCounterKeyBuilder.Build(requestIdentity, rule);
             }
@@ -36,7 +33,5 @@ protected virtual string BuildCounterKey(ClientRequestIdentity requestIdentity,
 
             return Convert.ToBase64String(hash);
         }
-
-        public abstract Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, CancellationToken cancellationToken = default);
     }
 }
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/ProcessingStrategyFactory.cs b/src/AspNetCoreRateLimit/Core/ProcessingStrategies/ProcessingStrategyFactory.cs
deleted file mode 100644
index a8b7952d..00000000
--- a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/ProcessingStrategyFactory.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using StackExchange.Redis;
-
-namespace AspNetCoreRateLimit
-{
-
-    public class ProcessingStrategyFactory : IProcessingStrategyFactory
-    {
-        private readonly IConnectionMultiplexer _connectionMultiplexer;
-
-        public ProcessingStrategyFactory(IConnectionMultiplexer connectionMultiplexer = null)
-        {
-            _connectionMultiplexer = connectionMultiplexer;
-        }
-
-        public ProcessingStrategy CreateProcessingStrategy(IRateLimitCounterStore counterStore, ICounterKeyBuilder counterKeyBuilder, IRateLimitConfiguration config, RateLimitOptions options)
-        {
-            return counterStore switch
-            {
-                MemoryCacheRateLimitCounterStore => new AsyncKeyLockProcessingStrategy(counterStore, counterKeyBuilder, config, options),
-                DistributedCacheRateLimitCounterStore => new AsyncKeyLockProcessingStrategy(counterStore, counterKeyBuilder, config, options),
-                StackExchangeRedisRateLimitCounterStore => new StackExchangeRedisProcessingStrategy(_connectionMultiplexer, counterStore, counterKeyBuilder, config, options),
-                _ => throw new ArgumentException("Unsupported instance of IRateLimitCounterStore provided")
-            };
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/StackExchangeRedisProcessingStrategy.cs b/src/AspNetCoreRateLimit/Core/ProcessingStrategies/StackExchangeRedisProcessingStrategy.cs
deleted file mode 100644
index ecb73215..00000000
--- a/src/AspNetCoreRateLimit/Core/ProcessingStrategies/StackExchangeRedisProcessingStrategy.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using StackExchange.Redis;
-
-namespace AspNetCoreRateLimit
-{
-    public class StackExchangeRedisProcessingStrategy : ProcessingStrategy
-    {
-        private readonly IConnectionMultiplexer _connectionMultiplexer;
-        private readonly IRateLimitConfiguration _config;
-
-        public StackExchangeRedisProcessingStrategy(IConnectionMultiplexer connectionMultiplexer, IRateLimitCounterStore counterStore, ICounterKeyBuilder counterKeyBuilder, IRateLimitConfiguration config, RateLimitOptions options)
-            : base(counterKeyBuilder, config, options)
-        {
-            _connectionMultiplexer = connectionMultiplexer ?? throw new ArgumentException("IConnectionMultiplexer was null. Ensure StackExchange.Redis was successfully registered");
-            _config = config;
-        }
-
-
-        static private readonly LuaScript _atomicIncrement = LuaScript.Prepare("local count count = redis.call(\"INCRBYFLOAT\", @key, tonumber(@delta)) if tonumber(count) == @delta then redis.call(\"EXPIRE\", @key, @timeout) end return count");
-
-        public override async Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, CancellationToken cancellationToken = default)
-        {
-            var counterId = BuildCounterKey(requestIdentity, rule);
-            return await IncrementAsync(counterId, rule.PeriodTimespan.Value, _config.RateIncrementer);
-        }
-
-        public async Task<RateLimitCounter> IncrementAsync(string counterId, TimeSpan interval, Func<double> RateIncrementer = null)
-        {
-            var now = DateTime.UtcNow;
-            var numberOfIntervals = now.Ticks / interval.Ticks;
-            var intervalStart = new DateTime(numberOfIntervals * interval.Ticks, DateTimeKind.Utc);
-
-            // Call the Lua script
-            var count = await _connectionMultiplexer.GetDatabase().ScriptEvaluateAsync(_atomicIncrement, new { key = counterId, timeout = interval.TotalSeconds, delta = RateIncrementer?.Invoke() ?? 1D });
-            return new RateLimitCounter
-            {
-                Count = (double)count,
-                Timestamp = intervalStart
-            };
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/AspNetCoreRateLimit/Middleware/ClientRateLimitMiddleware.cs
index f11781c9..ae52b790 100644
--- a/src/AspNetCoreRateLimit/Middleware/ClientRateLimitMiddleware.cs
+++ b/src/AspNetCoreRateLimit/Middleware/ClientRateLimitMiddleware.cs
@@ -9,13 +9,13 @@ public class ClientRateLimitMiddleware : RateLimitMiddleware<ClientRateLimitProc
         private readonly ILogger<ClientRateLimitMiddleware> _logger;
 
         public ClientRateLimitMiddleware(RequestDelegate next,
+            IProcessingStrategy processingStrategy,
             IOptions<ClientRateLimitOptions> options,
-            IProcessingStrategyFactory processingStrategyFactory,
             IRateLimitCounterStore counterStore,
             IClientPolicyStore policyStore,
             IRateLimitConfiguration config,
             ILogger<ClientRateLimitMiddleware> logger)
-        : base(next, options?.Value, new ClientRateLimitProcessor(processingStrategyFactory, options?.Value, counterStore, policyStore, config), config)
+        : base(next, options?.Value, new ClientRateLimitProcessor(options?.Value, counterStore, policyStore, config, processingStrategy), config)
         {
             _logger = logger;
         }
diff --git a/src/AspNetCoreRateLimit/Middleware/IpRateLimitMiddleware.cs b/src/AspNetCoreRateLimit/Middleware/IpRateLimitMiddleware.cs
index 7b6a338a..86e1bb1e 100644
--- a/src/AspNetCoreRateLimit/Middleware/IpRateLimitMiddleware.cs
+++ b/src/AspNetCoreRateLimit/Middleware/IpRateLimitMiddleware.cs
@@ -9,14 +9,14 @@ public class IpRateLimitMiddleware : RateLimitMiddleware<IpRateLimitProcessor>
         private readonly ILogger<IpRateLimitMiddleware> _logger;
 
         public IpRateLimitMiddleware(RequestDelegate next,
-            IProcessingStrategyFactory processingStrategyFactory,
+            IProcessingStrategy processingStrategy,
             IOptions<IpRateLimitOptions> options,
             IRateLimitCounterStore counterStore,
             IIpPolicyStore policyStore,
             IRateLimitConfiguration config,
-            ILogger<IpRateLimitMiddleware> logger)
-        : base(next, options?.Value, new IpRateLimitProcessor(processingStrategyFactory, options?.Value, counterStore, policyStore, config), config)
-
+            ILogger<IpRateLimitMiddleware> logger
+        )
+            : base(next, options?.Value, new IpRateLimitProcessor(options?.Value, counterStore, policyStore, config, processingStrategy), config)
         {
             _logger = logger;
         }
diff --git a/src/AspNetCoreRateLimit/StartupExtensions.cs b/src/AspNetCoreRateLimit/StartupExtensions.cs
index 1cc9615c..bfe493ae 100644
--- a/src/AspNetCoreRateLimit/StartupExtensions.cs
+++ b/src/AspNetCoreRateLimit/StartupExtensions.cs
@@ -10,7 +10,7 @@ public static IServiceCollection AddMemoryCacheStores(this IServiceCollection se
             services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
             services.AddSingleton<IClientPolicyStore, MemoryCacheClientPolicyStore>();
             services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
-            services.AddSingleton<IProcessingStrategyFactory, ProcessingStrategyFactory>();
+            services.AddSingleton<IProcessingStrategy, AsyncKeyLockProcessingStrategy>();
             return services;
         }
 
@@ -19,15 +19,7 @@ public static IServiceCollection AddDistributedCacheStores(this IServiceCollecti
             services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>();
             services.AddSingleton<IClientPolicyStore, DistributedCacheClientPolicyStore>();
             services.AddSingleton<IRateLimitCounterStore, DistributedCacheRateLimitCounterStore>();
-            return services;
-        }
-
-        public static IServiceCollection AddStackExchangeRedisStores(this IServiceCollection services)
-        {
-            services.AddSingleton<IProcessingStrategyFactory, ProcessingStrategyFactory>();
-            services.AddSingleton<IIpPolicyStore, StackExchangeRedisIpPolicyStore>();
-            services.AddSingleton<IClientPolicyStore, StackExchangeRedisClientPolicyStore>();
-            services.AddSingleton<IRateLimitCounterStore, StackExchangeRedisRateLimitCounterStore>();
+            services.AddSingleton<IProcessingStrategy, AsyncKeyLockProcessingStrategy>();
             return services;
         }
 
diff --git a/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisClientPolicyStore.cs b/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisClientPolicyStore.cs
deleted file mode 100644
index 992d503e..00000000
--- a/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisClientPolicyStore.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System.Threading.Tasks;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.Options;
-using StackExchange.Redis;
-
-namespace AspNetCoreRateLimit
-{
-    public class StackExchangeRedisClientPolicyStore : StackExchangeRedisRateLimitStore<ClientRateLimitPolicy>, IClientPolicyStore
-    {
-        private readonly ClientRateLimitOptions _options;
-        private readonly ClientRateLimitPolicies _policies;
-
-        public StackExchangeRedisClientPolicyStore(
-            IConnectionMultiplexer redis,
-            IOptions<ClientRateLimitOptions> options = null,
-            IOptions<ClientRateLimitPolicies> policies = null) : base(redis)
-        {
-            _options = options?.Value;
-            _policies = policies?.Value;
-        }
-
-        public async Task SeedAsync()
-        {
-            // on startup, save the IP rules defined in appsettings
-            if (_options != null && _policies?.ClientRules != null)
-            {
-                foreach (var rule in _policies.ClientRules)
-                {
-                    await SetAsync($"{_options.ClientPolicyPrefix}_{rule.ClientId}", new ClientRateLimitPolicy { ClientId = rule.ClientId, Rules = rule.Rules }).ConfigureAwait(false);
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisIpPolicyStore.cs b/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisIpPolicyStore.cs
deleted file mode 100644
index 8fa36a4d..00000000
--- a/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisIpPolicyStore.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Threading.Tasks;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.Options;
-using StackExchange.Redis;
-
-namespace AspNetCoreRateLimit
-{
-    public class StackExchangeRedisIpPolicyStore : StackExchangeRedisRateLimitStore<IpRateLimitPolicies>, IIpPolicyStore
-    {
-        private readonly IpRateLimitOptions _options;
-        private readonly IpRateLimitPolicies _policies;
-
-        public StackExchangeRedisIpPolicyStore(
-            IConnectionMultiplexer redis,
-            IOptions<IpRateLimitOptions> options = null,
-            IOptions<IpRateLimitPolicies> policies = null) : base(redis)
-        {
-            _options = options?.Value;
-            _policies = policies?.Value;
-        }
-
-        public async Task SeedAsync()
-        {
-            // on startup, save the IP rules defined in appsettings
-            if (_options != null && _policies != null)
-            {
-                await SetAsync($"{_options.IpPolicyPrefix}", _policies).ConfigureAwait(false);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisRateLimitCounterStore.cs b/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisRateLimitCounterStore.cs
deleted file mode 100644
index ab32322f..00000000
--- a/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisRateLimitCounterStore.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using StackExchange.Redis;
-
-namespace AspNetCoreRateLimit
-{
-    public class StackExchangeRedisRateLimitCounterStore : StackExchangeRedisRateLimitStore<RateLimitCounter?>, IRateLimitCounterStore
-    {
-        public StackExchangeRedisRateLimitCounterStore(IConnectionMultiplexer connectionMultiplexer)
-            : base(connectionMultiplexer)
-        {
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisRateLimitStore.cs b/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisRateLimitStore.cs
deleted file mode 100644
index 569f4a57..00000000
--- a/src/AspNetCoreRateLimit/Store/StackExchangeRedis/StackExchangeRedisRateLimitStore.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using Newtonsoft.Json;
-using StackExchange.Redis;
-using System;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace AspNetCoreRateLimit
-{
-    public class StackExchangeRedisRateLimitStore<T> : IRateLimitStore<T>
-    {
-        private readonly IConnectionMultiplexer _redis;
-
-        public StackExchangeRedisRateLimitStore(IConnectionMultiplexer redis)
-        {
-            _redis = redis ?? throw new ArgumentNullException(nameof(redis));
-        }
-
-        public async Task SetAsync(string id, T entry, TimeSpan? expirationTime = null, CancellationToken cancellationToken = default)
-        {
-            // Throw an exception if the key could not be set
-            if (!await _redis.GetDatabase().StringSetAsync(id, JsonConvert.SerializeObject(entry), expirationTime))
-            {
-                throw new ExternalException($"Failed to set key {id}");
-            }
-        }
-
-        public Task<bool> ExistsAsync(string id, CancellationToken cancellationToken = default)
-        {
-            return _redis.GetDatabase().KeyExistsAsync(id);
-        }
-
-        public async Task<T> GetAsync(string id, CancellationToken cancellationToken = default)
-        {
-            var stored = await _redis.GetDatabase().StringGetAsync(id);
-            if (stored.HasValue)
-            {
-                return JsonConvert.DeserializeObject<T>(stored.ToString());
-            }
-
-            return default;
-        }
-
-        public Task RemoveAsync(string id, CancellationToken cancellationToken = default)
-        {
-            // Don't throw an exception if the key doesn't exist
-            return _redis.GetDatabase().KeyDeleteAsync(id);
-        }
-    }
-}
\ No newline at end of file