From f3392f9fa4d502111d7a0d8dc0d3ad1dc5f900f8 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 8 Feb 2022 11:30:53 -0500 Subject: [PATCH 1/3] Proxy: move to extension methods This should make PRs like #1977 and #1425 much easier. --- .../ConnectionMultiplexer.cs | 42 ++++++++++++------- src/StackExchange.Redis/Enums/Proxy.cs | 30 +++++++++++++ src/StackExchange.Redis/Enums/ServerType.cs | 20 +++++++++ .../Interfaces/IConnectionMultiplexer.cs | 2 +- 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs index 3b827f7f8..c7e02416d 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs @@ -569,7 +569,7 @@ private static void WriteNormalizingLineEndings(string source, StreamWriter writ /// /// Raised when nodes are explicitly requested to reconfigure via broadcast. - /// This usually means primary/replica role changes. + /// This usually means primary/replica changes. /// public event EventHandler ConfigurationChangedBroadcast; @@ -1418,7 +1418,10 @@ internal long LastHeartbeatSecondsAgo /// The async state object to pass to the created . public ISubscriber GetSubscriber(object asyncState = null) { - if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The pub/sub API is not available via twemproxy"); + if (!RawConfig.Proxy.SupportsPubSub()) + { + throw new NotSupportedException($"The pub/sub API is not available via {RawConfig.Proxy}"); + } return new RedisSubscriber(this, asyncState); } @@ -1436,9 +1439,9 @@ internal int ApplyDefaultDatabase(int db) throw new ArgumentOutOfRangeException(nameof(db)); } - if (db != 0 && RawConfig.Proxy == Proxy.Twemproxy) + if (db != 0 && !RawConfig.Proxy.SupportsDatabases()) { - throw new NotSupportedException("twemproxy only supports database 0"); + throw new NotSupportedException($"{RawConfig.Proxy} only supports database 0"); } return db; @@ -1506,7 +1509,10 @@ public IDatabase GetDatabase(int db = -1, object asyncState = null) public IServer GetServer(EndPoint endpoint, object asyncState = null) { if (endpoint == null) throw new ArgumentNullException(nameof(endpoint)); - if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The server API is not available via twemproxy"); + if (!RawConfig.Proxy.SupportsServerApi()) + { + throw new NotSupportedException($"The server API is not available via {RawConfig.Proxy}"); + } var server = (ServerEndPoint)servers[endpoint]; if (server == null) throw new ArgumentException("The specified endpoint is not defined", nameof(endpoint)); return new RedisServer(this, server, asyncState); @@ -1882,18 +1888,24 @@ internal async Task ReconfigureAsync(bool first, bool reconfigureAll, LogP ServerSelectionStrategy.ServerType = ServerType.Standalone; } - var preferred = NominatePreferredMaster(log, servers, useTieBreakers, masters); - foreach (var master in masters) + // If multiple primaries are detected, nominate the preferred one + // ...but not if the type of server we're connected to supports and expects multiple primaries + // ...for those cases, we want to allow sending to any primary endpoint. + if (ServerSelectionStrategy.ServerType.HasSinglePrimary()) { - if (master == preferred || master.IsReplica) - { - log?.WriteLine($"{Format.ToString(master)}: Clearing as RedundantMaster"); - master.ClearUnselectable(UnselectableFlags.RedundantMaster); - } - else + var preferred = NominatePreferredMaster(log, servers, useTieBreakers, masters); + foreach (var master in masters) { - log?.WriteLine($"{Format.ToString(master)}: Setting as RedundantMaster"); - master.SetUnselectable(UnselectableFlags.RedundantMaster); + if (master == preferred || master.IsReplica) + { + log?.WriteLine($"{Format.ToString(master)}: Clearing as RedundantMaster"); + master.ClearUnselectable(UnselectableFlags.RedundantMaster); + } + else + { + log?.WriteLine($"{Format.ToString(master)}: Setting as RedundantMaster"); + master.SetUnselectable(UnselectableFlags.RedundantMaster); + } } } } diff --git a/src/StackExchange.Redis/Enums/Proxy.cs b/src/StackExchange.Redis/Enums/Proxy.cs index 64dcada45..38a71a8e1 100644 --- a/src/StackExchange.Redis/Enums/Proxy.cs +++ b/src/StackExchange.Redis/Enums/Proxy.cs @@ -14,4 +14,34 @@ public enum Proxy /// Twemproxy, } + + internal static class ProxyExtensions + { + /// + /// Whether a proxy supports databases (e.g. database > 0). + /// + public static bool SupportsDatabases(this Proxy proxy) => proxy switch + { + Proxy.Twemproxy => false, + _ => true + }; + + /// + /// Whether a proxy supports pub/sub. + /// + public static bool SupportsPubSub(this Proxy proxy) => proxy switch + { + Proxy.Twemproxy => false, + _ => true + }; + + /// + /// Whether a proxy supports the ConnectionMultiplexer.GetServer. + /// + public static bool SupportsServerApi(this Proxy proxy) => proxy switch + { + Proxy.Twemproxy => false, + _ => true + }; + } } diff --git a/src/StackExchange.Redis/Enums/ServerType.cs b/src/StackExchange.Redis/Enums/ServerType.cs index c3b6abaf4..66f1fb71d 100644 --- a/src/StackExchange.Redis/Enums/ServerType.cs +++ b/src/StackExchange.Redis/Enums/ServerType.cs @@ -22,4 +22,24 @@ public enum ServerType /// Twemproxy, } + + internal static class ServerTypeExtensions + { + /// + /// Whether a server type can have only a single primary, meaning an election if multiple are found. + /// + public static bool HasSinglePrimary(this ServerType type) => type switch + { + _ => false + }; + + /// + /// Whether a server type supports . + /// + public static bool SupportsAutoConfigure(this ServerType type) => type switch + { + ServerType.Twemproxy => false, + _ => true + }; + } } diff --git a/src/StackExchange.Redis/Interfaces/IConnectionMultiplexer.cs b/src/StackExchange.Redis/Interfaces/IConnectionMultiplexer.cs index 1d11e3fbb..00ba41307 100644 --- a/src/StackExchange.Redis/Interfaces/IConnectionMultiplexer.cs +++ b/src/StackExchange.Redis/Interfaces/IConnectionMultiplexer.cs @@ -107,7 +107,7 @@ public interface IConnectionMultiplexer : IDisposable /// /// Raised when nodes are explicitly requested to reconfigure via broadcast. - /// This usually means primary/replica role changes. + /// This usually means primary/replica changes. /// event EventHandler ConfigurationChangedBroadcast; From 0381053b30674635eedb9a63d29573b3220a70f2 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 8 Feb 2022 11:39:02 -0500 Subject: [PATCH 2/3] Fix default! --- src/StackExchange.Redis/Enums/ServerType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StackExchange.Redis/Enums/ServerType.cs b/src/StackExchange.Redis/Enums/ServerType.cs index 66f1fb71d..b48c80aeb 100644 --- a/src/StackExchange.Redis/Enums/ServerType.cs +++ b/src/StackExchange.Redis/Enums/ServerType.cs @@ -30,7 +30,7 @@ internal static class ServerTypeExtensions /// public static bool HasSinglePrimary(this ServerType type) => type switch { - _ => false + _ => true }; /// From 463aba9bcd038d4276f7b2573026c11c03a78f77 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Wed, 9 Feb 2022 20:28:45 -0500 Subject: [PATCH 3/3] Missed a file! --- src/StackExchange.Redis/ServerEndPoint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs index 271d0c61c..0daa78986 100755 --- a/src/StackExchange.Redis/ServerEndPoint.cs +++ b/src/StackExchange.Redis/ServerEndPoint.cs @@ -329,10 +329,10 @@ internal void AddScript(string script, byte[] hash) internal async Task AutoConfigureAsync(PhysicalConnection connection, LogProxy log = null) { - if (serverType == ServerType.Twemproxy) + if (!serverType.SupportsAutoConfigure()) { - // don't try to detect configuration; all the config commands are disabled, and - // the fallback master/replica detection won't help + // Don't try to detect configuration. + // All the config commands are disabled and the fallback primary/replica detection won't help return; }