From 7c9faf8e01cde095cd8a4b7ad2c7fc639dcdfffc Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 9 Dec 2025 11:37:53 -0500 Subject: [PATCH 1/5] Options: Split out AMR to its own options provider This breaks out AMR into its own default options provider to specify default versions but also allow us to do things like specify default RESP later on. --- .../AzureManagedRedisOptionsProvider.cs | 61 +++++++++++++++++++ .../Configuration/AzureOptionsProvider.cs | 18 +----- .../PublicAPI/PublicAPI.Shipped.txt | 7 +++ src/StackExchange.Redis/RedisFeatures.cs | 1 + 4 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs diff --git a/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs b/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs new file mode 100644 index 000000000..863d9533d --- /dev/null +++ b/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs @@ -0,0 +1,61 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using StackExchange.Redis.Maintenance; + +namespace StackExchange.Redis.Configuration +{ + /// + /// Options provider for Azure Managed Redis environments. + /// + public class AzureManagedRedisOptionsProvider : DefaultOptionsProvider + { + /// + /// Allow connecting after startup, in the cases where remote cache isn't ready or is overloaded. + /// + public override bool AbortOnConnectFail => false; + + /// + /// The minimum version of Redis in Azure Managed Redis is 7.4, so use the widest set of available commands when connecting. + /// + public override Version DefaultVersion => RedisFeatures.v7_4_0; + + private static readonly string[] azureManagedRedisDomains = + [ + ".redis.azure.net", + ".redis.chinacloudapi.cn", + ".redis.usgovcloudapi.net", + ]; + + /// + public override bool IsMatch(EndPoint endpoint) + { + if (endpoint is DnsEndPoint dnsEp && IsHostInDomains(dnsEp.Host, azureManagedRedisDomains)) + { + return true; + } + + return false; + } + + private bool IsHostInDomains(string hostName, string[] domains) + { + foreach (var domain in domains) + { + if (hostName.EndsWith(domain, StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + } + + return false; + } + + /// + public override Task AfterConnectAsync(ConnectionMultiplexer muxer, Action log) + => AzureMaintenanceEvent.AddListenerAsync(muxer, log); + + /// + public override bool GetDefaultSsl(EndPointCollection endPoints) => true; + } +} diff --git a/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs index fb01f0704..625ac899b 100644 --- a/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs @@ -32,22 +32,12 @@ public class AzureOptionsProvider : DefaultOptionsProvider ".redisenterprise.cache.azure.net", }; - private static readonly string[] azureManagedRedisDomains = new[] - { - ".redis.azure.net", - ".redis.chinacloudapi.cn", - ".redis.usgovcloudapi.net", - }; - /// public override bool IsMatch(EndPoint endpoint) { - if (endpoint is DnsEndPoint dnsEp) + if (endpoint is DnsEndPoint dnsEp && IsHostInDomains(dnsEp.Host, azureRedisDomains)) { - if (IsHostInDomains(dnsEp.Host, azureRedisDomains) || IsHostInDomains(dnsEp.Host, azureManagedRedisDomains)) - { - return true; - } + return true; } return false; @@ -82,10 +72,6 @@ public override bool GetDefaultSsl(EndPointCollection endPoints) { return true; } - if (dns.Port == 10000 && IsHostInDomains(dns.Host, azureManagedRedisDomains)) - { - return true; // SSL is enabled by default on AMR caches - } break; case IPEndPoint ip: if (ip.Port == 6380) diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 5eaa42b3f..905ce56d7 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -14,6 +14,11 @@ override StackExchange.Redis.Configuration.AzureOptionsProvider.AfterConnectAsyn override StackExchange.Redis.Configuration.AzureOptionsProvider.DefaultVersion.get -> System.Version! override StackExchange.Redis.Configuration.AzureOptionsProvider.GetDefaultSsl(StackExchange.Redis.EndPointCollection! endPoints) -> bool override StackExchange.Redis.Configuration.AzureOptionsProvider.IsMatch(System.Net.EndPoint! endpoint) -> bool +override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.AbortOnConnectFail.get -> bool +override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.AfterConnectAsync(StackExchange.Redis.ConnectionMultiplexer! muxer, System.Action! log) -> System.Threading.Tasks.Task! +override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.DefaultVersion.get -> System.Version! +override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.GetDefaultSsl(StackExchange.Redis.EndPointCollection! endPoints) -> bool +override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.IsMatch(System.Net.EndPoint! endpoint) -> bool override StackExchange.Redis.ConfigurationOptions.ToString() -> string! override StackExchange.Redis.ConnectionCounters.ToString() -> string! override StackExchange.Redis.ConnectionFailedEventArgs.ToString() -> string! @@ -199,6 +204,8 @@ StackExchange.Redis.ConditionResult StackExchange.Redis.ConditionResult.WasSatisfied.get -> bool StackExchange.Redis.Configuration.AzureOptionsProvider StackExchange.Redis.Configuration.AzureOptionsProvider.AzureOptionsProvider() -> void +StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider +StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.AzureManagedRedisOptionsProvider() -> void StackExchange.Redis.Configuration.DefaultOptionsProvider StackExchange.Redis.Configuration.DefaultOptionsProvider.ClientName.get -> string! StackExchange.Redis.Configuration.DefaultOptionsProvider.DefaultOptionsProvider() -> void diff --git a/src/StackExchange.Redis/RedisFeatures.cs b/src/StackExchange.Redis/RedisFeatures.cs index 0e6b410a9..d097e418c 100644 --- a/src/StackExchange.Redis/RedisFeatures.cs +++ b/src/StackExchange.Redis/RedisFeatures.cs @@ -45,6 +45,7 @@ namespace StackExchange.Redis v7_2_0_rc1 = new Version(7, 1, 240), // 7.2 RC1 is version 7.1.240 v7_4_0_rc1 = new Version(7, 3, 240), // 7.4 RC1 is version 7.3.240 v7_4_0_rc2 = new Version(7, 3, 241), // 7.4 RC2 is version 7.3.241 + v7_4_0 = new Version(7, 4, 0), v8_0_0_M04 = new Version(7, 9, 227), // 8.0 M04 is version 7.9.227 v8_2_0_rc1 = new Version(8, 1, 240), // 8.2 RC1 is version 8.1.240 v8_4_0_rc1 = new Version(8, 3, 224); // 8.4 RC1 is version 8.3.224 From 9eccdc64a9870167e8c6bfbafe9f462eff726da5 Mon Sep 17 00:00:00 2001 From: Philo Date: Tue, 20 Jan 2026 13:58:20 -0800 Subject: [PATCH 2/5] Add AMR to BuildInProviders --- src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs index 703adbcac..e4fa25891 100644 --- a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs @@ -26,6 +26,7 @@ public class DefaultOptionsProvider private static readonly List BuiltInProviders = new() { new AzureOptionsProvider(), + new AzureManagedRedisOptionsProvider(), }; /// From 2eedeb9fa971f6bec03976811c16a953fac5bad6 Mon Sep 17 00:00:00 2001 From: Philo Date: Tue, 20 Jan 2026 13:58:58 -0800 Subject: [PATCH 3/5] Add new cloud endpoints for OSS Azure Redis --- src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs index 625ac899b..33e4e8106 100644 --- a/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs @@ -29,6 +29,8 @@ public class AzureOptionsProvider : DefaultOptionsProvider ".redis.cache.windows.net", ".redis.cache.chinacloudapi.cn", ".redis.cache.usgovcloudapi.net", + ".redis.cache.sovcloud-api.de", + ".redis.cache.sovcloud-api.fr", ".redisenterprise.cache.azure.net", }; From b9fc5a4ca52e7c3106d28ebc11953744235eebdd Mon Sep 17 00:00:00 2001 From: Philo Date: Tue, 20 Jan 2026 16:29:24 -0800 Subject: [PATCH 4/5] Fix/add DefaultOptions tests --- tests/StackExchange.Redis.Tests/ConfigTests.cs | 18 +++++++++++++++--- .../DefaultOptionsTests.cs | 16 +++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/tests/StackExchange.Redis.Tests/ConfigTests.cs b/tests/StackExchange.Redis.Tests/ConfigTests.cs index 0c9286e17..3dfa4f99a 100644 --- a/tests/StackExchange.Redis.Tests/ConfigTests.cs +++ b/tests/StackExchange.Redis.Tests/ConfigTests.cs @@ -131,13 +131,25 @@ public void SslProtocols_InvalidValue() [InlineData("contoso.redis.cache.windows.net:6380", true)] [InlineData("contoso.REDIS.CACHE.chinacloudapi.cn:6380", true)] // added a few upper case chars to validate comparison [InlineData("contoso.redis.cache.usgovcloudapi.net:6380", true)] - [InlineData("contoso.redisenterprise.cache.azure.net:10000", false)] + [InlineData("contoso.redis.cache.sovcloud-api.de:6380", true)] + [InlineData("contoso.redis.cache.sovcloud-api.fr:6380", true)] + public void ConfigurationOptionsDefaultForAzure(string hostAndPort, bool sslShouldBeEnabled) + { + Version defaultAzureVersion = new(6, 0, 0); + var options = ConfigurationOptions.Parse(hostAndPort); + Assert.True(options.DefaultVersion.Equals(defaultAzureVersion)); + Assert.False(options.AbortOnConnectFail); + Assert.Equal(sslShouldBeEnabled, options.Ssl); + } + + [Theory] [InlineData("contoso.redis.azure.net:10000", true)] [InlineData("contoso.redis.chinacloudapi.cn:10000", true)] [InlineData("contoso.redis.usgovcloudapi.net:10000", true)] - public void ConfigurationOptionsDefaultForAzure(string hostAndPort, bool sslShouldBeEnabled) + [InlineData("contoso.redisenterprise.cache.azure.net:10000", true)] + public void ConfigurationOptionsDefaultForAzureManagedRedis(string hostAndPort, bool sslShouldBeEnabled) { - Version defaultAzureVersion = new(6, 0, 0); + Version defaultAzureVersion = new(7, 4, 0); var options = ConfigurationOptions.Parse(hostAndPort); Assert.True(options.DefaultVersion.Equals(defaultAzureVersion)); Assert.False(options.AbortOnConnectFail); diff --git a/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs index be80fd9c5..a01e845da 100644 --- a/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs +++ b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs @@ -65,15 +65,25 @@ public void IsMatchOnDomain() [InlineData("contoso.redis.cache.windows.net")] [InlineData("contoso.REDIS.CACHE.chinacloudapi.cn")] // added a few upper case chars to validate comparison [InlineData("contoso.redis.cache.usgovcloudapi.net")] - [InlineData("contoso.redisenterprise.cache.azure.net")] + [InlineData("contoso.redis.cache.sovcloud-api.de")] + [InlineData("contoso.redis.cache.sovcloud-api.fr")] + public void IsMatchOnAzureDomain(string hostName) + { + var epc = new EndPointCollection(new List() { new DnsEndPoint(hostName, 0) }); + var provider = DefaultOptionsProvider.GetProvider(epc); + Assert.IsType(provider); + } + + [Theory] [InlineData("contoso.redis.azure.net")] [InlineData("contoso.redis.chinacloudapi.cn")] [InlineData("contoso.redis.usgovcloudapi.net")] - public void IsMatchOnAzureDomain(string hostName) + [InlineData("contoso.redisenterprise.cache.azure.net")] + public void IsMatchOnAzureManagedRedisDomain(string hostName) { var epc = new EndPointCollection(new List() { new DnsEndPoint(hostName, 0) }); var provider = DefaultOptionsProvider.GetProvider(epc); - Assert.IsType(provider); + Assert.IsType(provider); } [Fact] From 521264e1f5040d55a041210d33cffcb7ade1fba2 Mon Sep 17 00:00:00 2001 From: Philo Date: Tue, 20 Jan 2026 16:36:47 -0800 Subject: [PATCH 5/5] Move Enterprise domain root from OSS to AMR --- .../Configuration/AzureManagedRedisOptionsProvider.cs | 1 + src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs b/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs index 863d9533d..06656b608 100644 --- a/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs @@ -25,6 +25,7 @@ public class AzureManagedRedisOptionsProvider : DefaultOptionsProvider ".redis.azure.net", ".redis.chinacloudapi.cn", ".redis.usgovcloudapi.net", + ".redisenterprise.cache.azure.net", ]; /// diff --git a/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs index 33e4e8106..c02f8f760 100644 --- a/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/AzureOptionsProvider.cs @@ -31,7 +31,6 @@ public class AzureOptionsProvider : DefaultOptionsProvider ".redis.cache.usgovcloudapi.net", ".redis.cache.sovcloud-api.de", ".redis.cache.sovcloud-api.fr", - ".redisenterprise.cache.azure.net", }; ///