From 39eac0123d9aca1ae2e3f905b15b8b0e6ec0aaa7 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 13 Feb 2024 16:07:31 +0000 Subject: [PATCH] Support Alibaba pseudo-cluster configurations (#2646) * fix #2642 1: don't treat trivial clusters as clusters - Alibaba uses this config 2: report synchronous failures immidiately and accurately * instead of using node count, use explicit tracking of the DB count * release notes --- docs/ReleaseNotes.md | 3 +- .../ConnectionMultiplexer.cs | 33 ++++++++++--------- src/StackExchange.Redis/PhysicalConnection.cs | 4 +++ src/StackExchange.Redis/ResultProcessor.cs | 11 ++++++- src/StackExchange.Redis/ServerEndPoint.cs | 4 +++ 5 files changed, 38 insertions(+), 17 deletions(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 46704f235..5c87ec5bc 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -7,7 +7,8 @@ Current package versions: | [![StackExchange.Redis](https://img.shields.io/nuget/v/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis](https://img.shields.io/nuget/vpre/StackExchange.Redis.svg)](https://www.nuget.org/packages/StackExchange.Redis/) | [![StackExchange.Redis MyGet](https://img.shields.io/myget/stackoverflow/vpre/StackExchange.Redis.svg)](https://www.myget.org/feed/stackoverflow/package/nuget/StackExchange.Redis) | ## Unreleased -No unreleased changes + +- Fix [#2642](https://github.com/StackExchange/StackExchange.Redis/issues/2642): Detect and support multi-DB pseudo-cluster/proxy scenarios ([#2646](https://github.com/StackExchange/StackExchange.Redis/pull/2646) by mgravell) ## 2.7.17 diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs index 44cc9f7c7..022ae8cd9 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs @@ -1,4 +1,7 @@ -using System; +using Microsoft.Extensions.Logging; +using Pipelines.Sockets.Unofficial; +using StackExchange.Redis.Profiling; +using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; @@ -11,9 +14,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Pipelines.Sockets.Unofficial; -using StackExchange.Redis.Profiling; namespace StackExchange.Redis { @@ -2078,19 +2078,22 @@ internal static void ThrowFailed(TaskCompletionSource? source, Exception u #pragma warning disable CS0618 // Type or member is obsolete result = TryPushMessageToBridgeSync(message, processor, source, ref server); #pragma warning restore CS0618 - if (result != WriteResult.Success) + if (!source.IsFaulted) // if we faulted while writing, we don't need to wait { - throw GetException(result, message, server); - } + if (result != WriteResult.Success) + { + throw GetException(result, message, server); + } - if (Monitor.Wait(source, TimeoutMilliseconds)) - { - Trace("Timely response to " + message); - } - else - { - Trace("Timeout performing " + message); - timeout = true; + if (Monitor.Wait(source, TimeoutMilliseconds)) + { + Trace("Timely response to " + message); + } + else + { + Trace("Timeout performing " + message); + timeout = true; + } } } diff --git a/src/StackExchange.Redis/PhysicalConnection.cs b/src/StackExchange.Redis/PhysicalConnection.cs index 70bc33c0d..9c467dcd3 100644 --- a/src/StackExchange.Redis/PhysicalConnection.cs +++ b/src/StackExchange.Redis/PhysicalConnection.cs @@ -95,6 +95,9 @@ public PhysicalConnection(PhysicalBridge bridge) OnCreateEcho(); } + // *definitely* multi-database; this can help identify some unusual config scenarios + internal bool MultiDatabasesOverride { get; set; } // switch to flags-enum if more needed later + internal async Task BeginConnectAsync(ILogger? log) { var bridge = BridgeCouldBeNull; @@ -262,6 +265,7 @@ private enum ReadMode : byte private RedisProtocol _protocol; // note starts at **zero**, not RESP2 public RedisProtocol? Protocol => _protocol == 0 ? null : _protocol; + internal void SetProtocol(RedisProtocol value) => _protocol = value; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] diff --git a/src/StackExchange.Redis/ResultProcessor.cs b/src/StackExchange.Redis/ResultProcessor.cs index 6fd229af1..95dc4bd87 100644 --- a/src/StackExchange.Redis/ResultProcessor.cs +++ b/src/StackExchange.Redis/ResultProcessor.cs @@ -929,6 +929,10 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes int dbCount = checked((int)i64); Log?.LogInformation($"{Format.ToString(server)}: Auto-configured (CONFIG) databases: " + dbCount); server.Databases = dbCount; + if (dbCount > 1) + { + connection.MultiDatabasesOverride = true; + } } else if (key.IsEqual(CommonReplies.slave_read_only) || key.IsEqual(CommonReplies.replica_read_only)) { @@ -1108,8 +1112,13 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes case ResultType.BulkString: string nodes = result.GetString()!; var bridge = connection.BridgeCouldBeNull; - if (bridge != null) bridge.ServerEndPoint.ServerType = ServerType.Cluster; var config = Parse(connection, nodes); + + // re multi-db: https://github.com/StackExchange/StackExchange.Redis/issues/2642 + if (bridge != null && !connection.MultiDatabasesOverride) + { + bridge.ServerEndPoint.ServerType = ServerType.Cluster; + } SetResult(message, config); return true; } diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs index ebb66ec2a..97230f85f 100644 --- a/src/StackExchange.Redis/ServerEndPoint.cs +++ b/src/StackExchange.Redis/ServerEndPoint.cs @@ -415,6 +415,10 @@ internal async Task AutoConfigureAsync(PhysicalConnection? connection, ILogger? lastInfoReplicationCheckTicks = Environment.TickCount; if (features.InfoSections) { + // note: Redis 7.0 has a multi-section usage, but we don't know + // the server version at this point; we *could* use the optional + // value on the config, but let's keep things simple: these + // commands are suitably cheap msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.replication); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait();