From 21aaab2b6046db0a71c4fc071a9196ffac4b9fd7 Mon Sep 17 00:00:00 2001 From: Philo Date: Thu, 9 Jul 2026 18:00:10 -0700 Subject: [PATCH] Support overriding library version --- .../Configuration/DefaultOptionsProvider.cs | 8 ++--- .../ConfigurationOptions.cs | 33 ++++++++++++++++--- .../ConnectionMultiplexer.LibraryName.cs | 17 +++------- .../PublicAPI/PublicAPI.Shipped.txt | 1 - .../PublicAPI/PublicAPI.Unshipped.txt | 3 ++ src/StackExchange.Redis/ServerEndPoint.cs | 2 +- .../StackExchange.Redis.Tests/ConfigTests.cs | 3 +- 7 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs index afcce011f..4b5b10f0a 100644 --- a/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs @@ -237,9 +237,9 @@ public static DefaultOptionsProvider GetProvider(EndPoint endpoint) /// Gets the default client name for a connection. /// protected virtual string GetDefaultClientName() => - (TryGetAzureRoleInstanceIdNoThrow() + $"{TryGetAzureRoleInstanceIdNoThrow() ?? ComputerName - ?? "StackExchange.Redis") + "(SE.Redis-v" + LibraryVersion + ")"; + ?? "StackExchange.Redis"}({LibraryName}-v{LibraryVersion})"; /// /// Gets the library name to use for CLIENT SETINFO lib-name calls to Redis during handshake. @@ -248,9 +248,9 @@ protected virtual string GetDefaultClientName() => public virtual string LibraryName => "SE.Redis"; /// - /// String version of the StackExchange.Redis library, for use in any options. + /// String version of the StackExchange.Redis library, for use in other options and in CLIENT SETINFO lib-ver. /// - protected static string LibraryVersion => Utils.GetLibVersion(); + public virtual string LibraryVersion => Utils.GetLibVersion(); /// /// Name of the machine we're running on, for use in any options. diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index eef0bde81..c582db0ed 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -116,7 +116,9 @@ internal const string SetClientLibrary = "setlib", Protocol = "protocol", HighIntegrity = "highIntegrity", - TcpKeepAlive = "tcpKeepAlive"; + TcpKeepAlive = "tcpKeepAlive", + LibraryName = "libraryName", + LibraryVersion = "libraryVersion"; private static readonly Dictionary normalizedOptions = new[] { @@ -152,6 +154,8 @@ internal const string Protocol, HighIntegrity, TcpKeepAlive, + LibraryName, + LibraryVersion, }.ToDictionary(x => x, StringComparer.OrdinalIgnoreCase); public static string TryNormalize(string value) @@ -209,7 +213,7 @@ private enum OptionFlags : ulong private OptionFlags optionFlags; - private string? tieBreaker, sslHost, configChannel, user, password; + private string? tieBreaker, sslHost, configChannel, user, password, libraryName, libraryVersion; private TimeSpan heartbeatInterval; @@ -368,7 +372,22 @@ public bool SetClientLibrary /// /// If the value is null, empty or whitespace, then the value from the options-provider is used; /// to disable the library name feature, use instead. - public string? LibraryName { get; set; } + public string? LibraryName + { + get => libraryName ?? Defaults.LibraryName; + set => libraryName = value; + } + + /// + /// Gets or sets the library version to use for CLIENT SETINFO lib-ver calls to Redis during handshake. + /// Defaults to the StackExchange.Redis version, but can be overridden by extensions or for other purposes. + /// + /// To disable the library version feature, use instead. + public string? LibraryVersion + { + get => libraryVersion ?? Defaults.LibraryVersion; + set => libraryVersion = value; + } /// /// Automatically encodes and decodes channels. @@ -967,7 +986,8 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow SslClientAuthenticationOptions = SslClientAuthenticationOptions, #endif Tunnel = Tunnel, - LibraryName = LibraryName, + libraryName = libraryName, + libraryVersion = libraryVersion, _protocol = _protocol, heartbeatInterval = heartbeatInterval, WriteMode = WriteMode, @@ -1063,6 +1083,8 @@ public string ToString(bool includePassword) { Append(sb, OptionKeys.Tunnel, tunnel.ToString()); } + Append(sb, OptionKeys.LibraryName, libraryName); + Append(sb, OptionKeys.LibraryVersion, libraryVersion); commandMap?.AppendDeltas(sb); return sb.ToString(); @@ -1164,7 +1186,8 @@ private void Clear() CertificateValidation = null; BeforeSocketConnect = null; ChannelPrefix = default; - LibraryName = null; + libraryName = null; + libraryVersion = null; #pragma warning disable CS0618 // Type or member is obsolete SocketManager = null; #pragma warning restore CS0618 // Type or member is obsolete diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.LibraryName.cs b/src/StackExchange.Redis/ConnectionMultiplexer.LibraryName.cs index 2c79f80c5..5c5167d24 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.LibraryName.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.LibraryName.cs @@ -14,6 +14,8 @@ public partial class ConnectionMultiplexer /// public void AddLibraryNameSuffix(string suffix) { + if (!RawConfig.SetClientLibrary) return; // disabled + if (string.IsNullOrWhiteSpace(suffix)) return; // trivial // sanitize and re-check @@ -28,7 +30,7 @@ public void AddLibraryNameSuffix(string suffix) } // if we get here, we *actually changed something*; we can retroactively fixup the connections - var libName = GetFullLibraryName(); // note this also checks SetClientLibrary + var libName = GetFullLibraryName(); if (string.IsNullOrWhiteSpace(libName) || !CommandMap.IsAvailable(RedisCommand.CLIENT)) return; // disabled on no lib name // note that during initial handshake we use raw Message; this is low frequency - no @@ -56,18 +58,9 @@ public void AddLibraryNameSuffix(string suffix) internal string GetFullLibraryName() { - var config = RawConfig; - if (!config.SetClientLibrary) return ""; // disabled - - var libName = config.LibraryName; - if (string.IsNullOrWhiteSpace(libName)) - { - // defer to provider if missing (note re null vs blank; if caller wants to disable - // it, they should set SetClientLibrary to false, not set the name to empty string) - libName = config.Defaults.LibraryName; - } - + var libName = RawConfig.LibraryName; libName = ServerEndPoint.ClientInfoSanitize(libName); + // if no primary name, return nothing, even if suffixes exist if (string.IsNullOrWhiteSpace(libName)) return ""; diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index cc104c577..bb8643aed 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -1693,7 +1693,6 @@ static StackExchange.Redis.Configuration.DefaultOptionsProvider.AddProvider(Stac static StackExchange.Redis.Configuration.DefaultOptionsProvider.ComputerName.get -> string! static StackExchange.Redis.Configuration.DefaultOptionsProvider.GetProvider(StackExchange.Redis.EndPointCollection! endpoints) -> StackExchange.Redis.Configuration.DefaultOptionsProvider! static StackExchange.Redis.Configuration.DefaultOptionsProvider.GetProvider(System.Net.EndPoint! endpoint) -> StackExchange.Redis.Configuration.DefaultOptionsProvider! -static StackExchange.Redis.Configuration.DefaultOptionsProvider.LibraryVersion.get -> string! static StackExchange.Redis.ConfigurationOptions.Parse(string! configuration) -> StackExchange.Redis.ConfigurationOptions! static StackExchange.Redis.ConfigurationOptions.Parse(string! configuration, bool ignoreUnknown) -> StackExchange.Redis.ConfigurationOptions! static StackExchange.Redis.ConnectionMultiplexer.Connect(StackExchange.Redis.ConfigurationOptions! configuration, System.IO.TextWriter? log = null) -> StackExchange.Redis.ConnectionMultiplexer! diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 7dc5c5811..cccc92436 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +StackExchange.Redis.ConfigurationOptions.LibraryVersion.get -> string? +StackExchange.Redis.ConfigurationOptions.LibraryVersion.set -> void +virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.LibraryVersion.get -> string! diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs index eca06f30d..49f78ebf0 100644 --- a/src/StackExchange.Redis/ServerEndPoint.cs +++ b/src/StackExchange.Redis/ServerEndPoint.cs @@ -1056,7 +1056,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log) await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); } - var version = ClientInfoSanitize(Utils.GetLibVersion()); + var version = ClientInfoSanitize(config.LibraryVersion); if (!string.IsNullOrWhiteSpace(version)) { msg = Message.Create(-1, CommandFlags.FireAndForget | Message.NoFlushFlag, RedisCommand.CLIENT, RedisLiterals.SETINFO, RedisLiterals.lib_ver, version); diff --git a/tests/StackExchange.Redis.Tests/ConfigTests.cs b/tests/StackExchange.Redis.Tests/ConfigTests.cs index 89ffd851e..a4b16d3f8 100644 --- a/tests/StackExchange.Redis.Tests/ConfigTests.cs +++ b/tests/StackExchange.Redis.Tests/ConfigTests.cs @@ -81,7 +81,8 @@ orderby name "EndPoints", "heartbeatInterval", "keepAlive", - "LibraryName", + "libraryName", + "libraryVersion", "loggerFactory", "optionFlags", #if DEBUG