Skip to content

Commit 1364ef8

Browse files
mgravellNickCraver
andauthored
Add support for CLIENT SETINFO (#2414)
Includes: - ConfigurationOptions (to opt-opt) - handshake (to send) - release notes - configuration documentation note we can't validate this yet as not on any released servers most contentious point: what lib-name to use - I've gone with `SE.Redis`, but: happy to use `StackExchange.Redis` if people prefer; I'm *not* aiming to make the name configurable cross-reference: redis/redis#11758 Co-authored-by: Nick Craver <[email protected]>
1 parent 3f8fd08 commit 1364ef8

File tree

9 files changed

+91
-4
lines changed

9 files changed

+91
-4
lines changed

docs/Configuration.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ The `ConfigurationOptions` object has a wide range of properties, all of which a
9696
| asyncTimeout={int} | `AsyncTimeout` | `SyncTimeout` | Time (ms) to allow for asynchronous operations |
9797
| tiebreaker={string} | `TieBreaker` | `__Booksleeve_TieBreak` | Key to use for selecting a server in an ambiguous primary scenario |
9898
| version={string} | `DefaultVersion` | (`4.0` in Azure, else `2.0`) | Redis version level (useful when the server does not make this available) |
99-
| tunnel={string} | `Tunnel` | `null` | Tunnel for connections (use `http:{proxy url}` for "connect"-based proxy server)
99+
| tunnel={string} | `Tunnel` | `null` | Tunnel for connections (use `http:{proxy url}` for "connect"-based proxy server) |
100+
| setlib={bool} | `SetClientLibrary` | `true` | Whether to attempt to use `CLIENT SETINFO` to set the lib name/version on the connection |
100101

101102
Additional code-only options:
102103
- ReconnectRetryPolicy (`IReconnectRetryPolicy`) - Default: `ReconnectRetryPolicy = ExponentialRetry(ConnectTimeout / 2);`

docs/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Current package versions:
99
## Unreleased
1010

1111
- Fix [#2400](https://github.com/StackExchange/StackExchange.Redis/issues/2400): Expose `ChannelMessageQueue` as `IAsyncEnumerable<ChannelMessage>` ([#2402 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2402))
12+
- Add: support for `CLIENT SETINFO` (lib name/version) during handshake; opt-out is via `ConfigurationOptions`; also support read of `resp`, `lib-ver` and `lib-name` via `CLIENT LIST` ([#2414 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2414))
1213

1314
## 2.6.96
1415

src/StackExchange.Redis/ClientInfo.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,23 @@ public ClientType ClientType
180180
}
181181
}
182182

183+
/// <summary>
184+
/// Client RESP protocol version. Added in Redis 7.0
185+
/// </summary>
186+
public string? ProtocolVersion { get; private set; }
187+
188+
/// <summary>
189+
/// Client library name. Added in Redis 7.2
190+
/// </summary>
191+
/// <remarks><seealso href="https://redis.io/commands/client-setinfo"/></remarks>
192+
public string? LibraryName { get; private set; }
193+
194+
/// <summary>
195+
/// Client library version. Added in Redis 7.2
196+
/// </summary>
197+
/// <remarks><seealso href="https://redis.io/commands/client-setinfo"/></remarks>
198+
public string? LibraryVersion { get; private set; }
199+
183200
internal static bool TryParse(string? input, [NotNullWhen(true)] out ClientInfo[]? clientList)
184201
{
185202
if (input == null)
@@ -241,6 +258,9 @@ internal static bool TryParse(string? input, [NotNullWhen(true)] out ClientInfo[
241258
client.Flags = flags;
242259
break;
243260
case "id": client.Id = Format.ParseInt64(value); break;
261+
case "resp": client.ProtocolVersion = value; break;
262+
case "lib-name": client.LibraryName = value; break;
263+
case "lib-ver": client.LibraryVersion = value; break;
244264
}
245265
}
246266
clients.Add(client);

src/StackExchange.Redis/Configuration/DefaultOptionsProvider.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ protected virtual string GetDefaultClientName() =>
197197
/// </summary>
198198
protected static string ComputerName => Environment.MachineName ?? Environment.GetEnvironmentVariable("ComputerName") ?? "Unknown";
199199

200+
/// <summary>
201+
/// Whether to identify the client by library name/version when possible
202+
/// </summary>
203+
public virtual bool SetClientLibrary => true;
204+
200205
/// <summary>
201206
/// Tries to get the RoleInstance Id if Microsoft.WindowsAzure.ServiceRuntime is loaded.
202207
/// In case of any failure, swallows the exception and returns null.

src/StackExchange.Redis/ConfigurationOptions.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ internal const string
9797
Version = "version",
9898
WriteBuffer = "writeBuffer",
9999
CheckCertificateRevocation = "checkCertificateRevocation",
100-
Tunnel = "tunnel";
100+
Tunnel = "tunnel",
101+
SetClientLibrary = "setlib";
101102

102103
private static readonly Dictionary<string, string> normalizedOptions = new[]
103104
{
@@ -142,7 +143,7 @@ public static string TryNormalize(string value)
142143
private DefaultOptionsProvider? defaultOptions;
143144

144145
private bool? allowAdmin, abortOnConnectFail, resolveDns, ssl, checkCertificateRevocation,
145-
includeDetailInExceptions, includePerformanceCountersInExceptions;
146+
includeDetailInExceptions, includePerformanceCountersInExceptions, setClientLibrary;
146147

147148
private string? tieBreaker, sslHost, configChannel;
148149

@@ -231,6 +232,15 @@ public bool UseSsl
231232
set => Ssl = value;
232233
}
233234

235+
/// <summary>
236+
/// Gets or sets whether the library should identify itself by library-name/version when possible
237+
/// </summary>
238+
public bool SetClientLibrary
239+
{
240+
get => setClientLibrary ?? Defaults.SetClientLibrary;
241+
set => setClientLibrary = value;
242+
}
243+
234244
/// <summary>
235245
/// Automatically encodes and decodes channels.
236246
/// </summary>
@@ -652,6 +662,7 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow
652662
SslClientAuthenticationOptions = SslClientAuthenticationOptions,
653663
#endif
654664
Tunnel = Tunnel,
665+
setClientLibrary = setClientLibrary,
655666
};
656667

657668
/// <summary>
@@ -731,6 +742,7 @@ public string ToString(bool includePassword)
731742
Append(sb, OptionKeys.ConfigCheckSeconds, configCheckSeconds);
732743
Append(sb, OptionKeys.ResponseTimeout, responseTimeout);
733744
Append(sb, OptionKeys.DefaultDatabase, DefaultDatabase);
745+
Append(sb, OptionKeys.SetClientLibrary, setClientLibrary);
734746
if (Tunnel is { IsInbuilt: true } tunnel)
735747
{
736748
Append(sb, OptionKeys.Tunnel, tunnel.ToString());
@@ -768,7 +780,7 @@ private void Clear()
768780
{
769781
ClientName = ServiceName = User = Password = tieBreaker = sslHost = configChannel = null;
770782
keepAlive = syncTimeout = asyncTimeout = connectTimeout = connectRetry = configCheckSeconds = DefaultDatabase = null;
771-
allowAdmin = abortOnConnectFail = resolveDns = ssl = null;
783+
allowAdmin = abortOnConnectFail = resolveDns = ssl = setClientLibrary = null;
772784
SslProtocols = null;
773785
defaultVersion = null;
774786
EndPoints.Clear();
@@ -778,6 +790,7 @@ private void Clear()
778790
CertificateValidation = null;
779791
ChannelPrefix = default;
780792
SocketManager = null;
793+
Tunnel = null;
781794
}
782795

783796
object ICloneable.Clone() => Clone();
@@ -883,6 +896,9 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown)
883896
case OptionKeys.SslProtocols:
884897
SslProtocols = OptionKeys.ParseSslProtocols(key, value);
885898
break;
899+
case OptionKeys.SetClientLibrary:
900+
SetClientLibrary = OptionKeys.ParseBoolean(key, value);
901+
break;
886902
case OptionKeys.Tunnel:
887903
if (value.IsNullOrWhiteSpace())
888904
{

src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,12 @@ StackExchange.Redis.ClientInfo.Host.get -> string?
118118
StackExchange.Redis.ClientInfo.Id.get -> long
119119
StackExchange.Redis.ClientInfo.IdleSeconds.get -> int
120120
StackExchange.Redis.ClientInfo.LastCommand.get -> string?
121+
StackExchange.Redis.ClientInfo.LibraryName.get -> string?
122+
StackExchange.Redis.ClientInfo.LibraryVersion.get -> string?
121123
StackExchange.Redis.ClientInfo.Name.get -> string?
122124
StackExchange.Redis.ClientInfo.PatternSubscriptionCount.get -> int
123125
StackExchange.Redis.ClientInfo.Port.get -> int
126+
StackExchange.Redis.ClientInfo.ProtocolVersion.get -> string?
124127
StackExchange.Redis.ClientInfo.Raw.get -> string?
125128
StackExchange.Redis.ClientInfo.SubscriptionCount.get -> int
126129
StackExchange.Redis.ClientInfo.TransactionCommandLength.get -> int
@@ -253,6 +256,8 @@ StackExchange.Redis.ConfigurationOptions.ResponseTimeout.get -> int
253256
StackExchange.Redis.ConfigurationOptions.ResponseTimeout.set -> void
254257
StackExchange.Redis.ConfigurationOptions.ServiceName.get -> string?
255258
StackExchange.Redis.ConfigurationOptions.ServiceName.set -> void
259+
StackExchange.Redis.ConfigurationOptions.SetClientLibrary.get -> bool
260+
StackExchange.Redis.ConfigurationOptions.SetClientLibrary.set -> void
256261
StackExchange.Redis.ConfigurationOptions.SetDefaultPorts() -> void
257262
StackExchange.Redis.ConfigurationOptions.SocketManager.get -> StackExchange.Redis.SocketManager?
258263
StackExchange.Redis.ConfigurationOptions.SocketManager.set -> void
@@ -1785,5 +1790,6 @@ virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.KeepAliveInterv
17851790
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.Proxy.get -> StackExchange.Redis.Proxy
17861791
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ReconnectRetryPolicy.get -> StackExchange.Redis.IReconnectRetryPolicy?
17871792
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ResolveDns.get -> bool
1793+
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.SetClientLibrary.get -> bool
17881794
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.SyncTimeout.get -> System.TimeSpan
17891795
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.TieBreaker.get -> string!

src/StackExchange.Redis/RedisLiterals.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ public static readonly RedisValue
8383
LATEST = "LATEST",
8484
LEFT = "LEFT",
8585
LEN = "LEN",
86+
lib_name = "lib-name",
87+
lib_ver = "lib-ver",
8688
LIMIT = "LIMIT",
8789
LIST = "LIST",
8890
LOAD = "LOAD",
@@ -118,8 +120,10 @@ public static readonly RedisValue
118120
REWRITE = "REWRITE",
119121
RIGHT = "RIGHT",
120122
SAVE = "SAVE",
123+
SE_Redis = "SE.Redis",
121124
SEGFAULT = "SEGFAULT",
122125
SET = "SET",
126+
SETINFO = "SETINFO",
123127
SETNAME = "SETNAME",
124128
SKIPME = "SKIPME",
125129
STATS = "STATS",

src/StackExchange.Redis/ServerEndPoint.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,26 @@ private async Task HandshakeAsync(PhysicalConnection connection, LogProxy? log)
929929
await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait();
930930
}
931931
}
932+
if (Multiplexer.RawConfig.SetClientLibrary)
933+
{
934+
// note that this is a relatively new feature, but usually we won't know the
935+
// server version, so we will use this speculatively and hope for the best
936+
log?.WriteLine($"{Format.ToString(this)}: Setting client lib/ver");
937+
938+
msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT,
939+
RedisLiterals.SETINFO, RedisLiterals.lib_name, RedisLiterals.SE_Redis);
940+
msg.SetInternalCall();
941+
await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait();
942+
943+
var version = Utils.GetLibVersion();
944+
if (!string.IsNullOrWhiteSpace(version))
945+
{
946+
msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT,
947+
RedisLiterals.SETINFO, RedisLiterals.lib_ver, version);
948+
msg.SetInternalCall();
949+
await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait();
950+
}
951+
}
932952
}
933953

934954
var bridge = connection.BridgeCouldBeNull;

tests/StackExchange.Redis.Tests/ConfigTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,4 +648,18 @@ public void CustomTunnelCanRoundtripMinusTunnel()
648648
options = ConfigurationOptions.Parse(cs);
649649
Assert.Null(options.Tunnel);
650650
}
651+
652+
[Theory]
653+
[InlineData("server:6379", true)]
654+
[InlineData("server:6379,setlib=True", true)]
655+
[InlineData("server:6379,setlib=False", false)]
656+
public void DefaultConfigOptionsForSetLib(string configurationString, bool setlib)
657+
{
658+
var options = ConfigurationOptions.Parse(configurationString);
659+
Assert.Equal(setlib, options.SetClientLibrary);
660+
Assert.Equal(configurationString, options.ToString());
661+
options = options.Clone();
662+
Assert.Equal(setlib, options.SetClientLibrary);
663+
Assert.Equal(configurationString, options.ToString());
664+
}
651665
}

0 commit comments

Comments
 (0)