Skip to content

Commit

Permalink
Add support for CLIENT SETINFO (#2414)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
mgravell and NickCraver authored Mar 28, 2023
1 parent 3f8fd08 commit 1364ef8
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 4 deletions.
3 changes: 2 additions & 1 deletion docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ The `ConfigurationOptions` object has a wide range of properties, all of which a
| asyncTimeout={int} | `AsyncTimeout` | `SyncTimeout` | Time (ms) to allow for asynchronous operations |
| tiebreaker={string} | `TieBreaker` | `__Booksleeve_TieBreak` | Key to use for selecting a server in an ambiguous primary scenario |
| version={string} | `DefaultVersion` | (`4.0` in Azure, else `2.0`) | Redis version level (useful when the server does not make this available) |
| tunnel={string} | `Tunnel` | `null` | Tunnel for connections (use `http:{proxy url}` for "connect"-based proxy server)
| tunnel={string} | `Tunnel` | `null` | Tunnel for connections (use `http:{proxy url}` for "connect"-based proxy server) |
| setlib={bool} | `SetClientLibrary` | `true` | Whether to attempt to use `CLIENT SETINFO` to set the lib name/version on the connection |

Additional code-only options:
- ReconnectRetryPolicy (`IReconnectRetryPolicy`) - Default: `ReconnectRetryPolicy = ExponentialRetry(ConnectTimeout / 2);`
Expand Down
1 change: 1 addition & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Current package versions:
## Unreleased

- 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))
- 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))

## 2.6.96

Expand Down
20 changes: 20 additions & 0 deletions src/StackExchange.Redis/ClientInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,23 @@ public ClientType ClientType
}
}

/// <summary>
/// Client RESP protocol version. Added in Redis 7.0
/// </summary>
public string? ProtocolVersion { get; private set; }

/// <summary>
/// Client library name. Added in Redis 7.2
/// </summary>
/// <remarks><seealso href="https://redis.io/commands/client-setinfo"/></remarks>
public string? LibraryName { get; private set; }

/// <summary>
/// Client library version. Added in Redis 7.2
/// </summary>
/// <remarks><seealso href="https://redis.io/commands/client-setinfo"/></remarks>
public string? LibraryVersion { get; private set; }

internal static bool TryParse(string? input, [NotNullWhen(true)] out ClientInfo[]? clientList)
{
if (input == null)
Expand Down Expand Up @@ -241,6 +258,9 @@ internal static bool TryParse(string? input, [NotNullWhen(true)] out ClientInfo[
client.Flags = flags;
break;
case "id": client.Id = Format.ParseInt64(value); break;
case "resp": client.ProtocolVersion = value; break;
case "lib-name": client.LibraryName = value; break;
case "lib-ver": client.LibraryVersion = value; break;
}
}
clients.Add(client);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ protected virtual string GetDefaultClientName() =>
/// </summary>
protected static string ComputerName => Environment.MachineName ?? Environment.GetEnvironmentVariable("ComputerName") ?? "Unknown";

/// <summary>
/// Whether to identify the client by library name/version when possible
/// </summary>
public virtual bool SetClientLibrary => true;

/// <summary>
/// Tries to get the RoleInstance Id if Microsoft.WindowsAzure.ServiceRuntime is loaded.
/// In case of any failure, swallows the exception and returns null.
Expand Down
22 changes: 19 additions & 3 deletions src/StackExchange.Redis/ConfigurationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ internal const string
Version = "version",
WriteBuffer = "writeBuffer",
CheckCertificateRevocation = "checkCertificateRevocation",
Tunnel = "tunnel";
Tunnel = "tunnel",
SetClientLibrary = "setlib";

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

private bool? allowAdmin, abortOnConnectFail, resolveDns, ssl, checkCertificateRevocation,
includeDetailInExceptions, includePerformanceCountersInExceptions;
includeDetailInExceptions, includePerformanceCountersInExceptions, setClientLibrary;

private string? tieBreaker, sslHost, configChannel;

Expand Down Expand Up @@ -231,6 +232,15 @@ public bool UseSsl
set => Ssl = value;
}

/// <summary>
/// Gets or sets whether the library should identify itself by library-name/version when possible
/// </summary>
public bool SetClientLibrary
{
get => setClientLibrary ?? Defaults.SetClientLibrary;
set => setClientLibrary = value;
}

/// <summary>
/// Automatically encodes and decodes channels.
/// </summary>
Expand Down Expand Up @@ -652,6 +662,7 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow
SslClientAuthenticationOptions = SslClientAuthenticationOptions,
#endif
Tunnel = Tunnel,
setClientLibrary = setClientLibrary,
};

/// <summary>
Expand Down Expand Up @@ -731,6 +742,7 @@ public string ToString(bool includePassword)
Append(sb, OptionKeys.ConfigCheckSeconds, configCheckSeconds);
Append(sb, OptionKeys.ResponseTimeout, responseTimeout);
Append(sb, OptionKeys.DefaultDatabase, DefaultDatabase);
Append(sb, OptionKeys.SetClientLibrary, setClientLibrary);
if (Tunnel is { IsInbuilt: true } tunnel)
{
Append(sb, OptionKeys.Tunnel, tunnel.ToString());
Expand Down Expand Up @@ -768,7 +780,7 @@ private void Clear()
{
ClientName = ServiceName = User = Password = tieBreaker = sslHost = configChannel = null;
keepAlive = syncTimeout = asyncTimeout = connectTimeout = connectRetry = configCheckSeconds = DefaultDatabase = null;
allowAdmin = abortOnConnectFail = resolveDns = ssl = null;
allowAdmin = abortOnConnectFail = resolveDns = ssl = setClientLibrary = null;
SslProtocols = null;
defaultVersion = null;
EndPoints.Clear();
Expand All @@ -778,6 +790,7 @@ private void Clear()
CertificateValidation = null;
ChannelPrefix = default;
SocketManager = null;
Tunnel = null;
}

object ICloneable.Clone() => Clone();
Expand Down Expand Up @@ -883,6 +896,9 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown)
case OptionKeys.SslProtocols:
SslProtocols = OptionKeys.ParseSslProtocols(key, value);
break;
case OptionKeys.SetClientLibrary:
SetClientLibrary = OptionKeys.ParseBoolean(key, value);
break;
case OptionKeys.Tunnel:
if (value.IsNullOrWhiteSpace())
{
Expand Down
6 changes: 6 additions & 0 deletions src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,12 @@ StackExchange.Redis.ClientInfo.Host.get -> string?
StackExchange.Redis.ClientInfo.Id.get -> long
StackExchange.Redis.ClientInfo.IdleSeconds.get -> int
StackExchange.Redis.ClientInfo.LastCommand.get -> string?
StackExchange.Redis.ClientInfo.LibraryName.get -> string?
StackExchange.Redis.ClientInfo.LibraryVersion.get -> string?
StackExchange.Redis.ClientInfo.Name.get -> string?
StackExchange.Redis.ClientInfo.PatternSubscriptionCount.get -> int
StackExchange.Redis.ClientInfo.Port.get -> int
StackExchange.Redis.ClientInfo.ProtocolVersion.get -> string?
StackExchange.Redis.ClientInfo.Raw.get -> string?
StackExchange.Redis.ClientInfo.SubscriptionCount.get -> int
StackExchange.Redis.ClientInfo.TransactionCommandLength.get -> int
Expand Down Expand Up @@ -253,6 +256,8 @@ StackExchange.Redis.ConfigurationOptions.ResponseTimeout.get -> int
StackExchange.Redis.ConfigurationOptions.ResponseTimeout.set -> void
StackExchange.Redis.ConfigurationOptions.ServiceName.get -> string?
StackExchange.Redis.ConfigurationOptions.ServiceName.set -> void
StackExchange.Redis.ConfigurationOptions.SetClientLibrary.get -> bool
StackExchange.Redis.ConfigurationOptions.SetClientLibrary.set -> void
StackExchange.Redis.ConfigurationOptions.SetDefaultPorts() -> void
StackExchange.Redis.ConfigurationOptions.SocketManager.get -> StackExchange.Redis.SocketManager?
StackExchange.Redis.ConfigurationOptions.SocketManager.set -> void
Expand Down Expand Up @@ -1785,5 +1790,6 @@ virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.KeepAliveInterv
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.Proxy.get -> StackExchange.Redis.Proxy
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ReconnectRetryPolicy.get -> StackExchange.Redis.IReconnectRetryPolicy?
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.ResolveDns.get -> bool
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.SetClientLibrary.get -> bool
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.SyncTimeout.get -> System.TimeSpan
virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.TieBreaker.get -> string!
4 changes: 4 additions & 0 deletions src/StackExchange.Redis/RedisLiterals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public static readonly RedisValue
LATEST = "LATEST",
LEFT = "LEFT",
LEN = "LEN",
lib_name = "lib-name",
lib_ver = "lib-ver",
LIMIT = "LIMIT",
LIST = "LIST",
LOAD = "LOAD",
Expand Down Expand Up @@ -118,8 +120,10 @@ public static readonly RedisValue
REWRITE = "REWRITE",
RIGHT = "RIGHT",
SAVE = "SAVE",
SE_Redis = "SE.Redis",
SEGFAULT = "SEGFAULT",
SET = "SET",
SETINFO = "SETINFO",
SETNAME = "SETNAME",
SKIPME = "SKIPME",
STATS = "STATS",
Expand Down
20 changes: 20 additions & 0 deletions src/StackExchange.Redis/ServerEndPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,26 @@ private async Task HandshakeAsync(PhysicalConnection connection, LogProxy? log)
await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait();
}
}
if (Multiplexer.RawConfig.SetClientLibrary)
{
// note that this is a relatively new feature, but usually we won't know the
// server version, so we will use this speculatively and hope for the best
log?.WriteLine($"{Format.ToString(this)}: Setting client lib/ver");

msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT,
RedisLiterals.SETINFO, RedisLiterals.lib_name, RedisLiterals.SE_Redis);
msg.SetInternalCall();
await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait();

var version = Utils.GetLibVersion();
if (!string.IsNullOrWhiteSpace(version))
{
msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT,
RedisLiterals.SETINFO, RedisLiterals.lib_ver, version);
msg.SetInternalCall();
await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait();
}
}
}

var bridge = connection.BridgeCouldBeNull;
Expand Down
14 changes: 14 additions & 0 deletions tests/StackExchange.Redis.Tests/ConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,18 @@ public void CustomTunnelCanRoundtripMinusTunnel()
options = ConfigurationOptions.Parse(cs);
Assert.Null(options.Tunnel);
}

[Theory]
[InlineData("server:6379", true)]
[InlineData("server:6379,setlib=True", true)]
[InlineData("server:6379,setlib=False", false)]
public void DefaultConfigOptionsForSetLib(string configurationString, bool setlib)
{
var options = ConfigurationOptions.Parse(configurationString);
Assert.Equal(setlib, options.SetClientLibrary);
Assert.Equal(configurationString, options.ToString());
options = options.Clone();
Assert.Equal(setlib, options.SetClientLibrary);
Assert.Equal(configurationString, options.ToString());
}
}

0 comments on commit 1364ef8

Please sign in to comment.