Skip to content

Commit 88e5375

Browse files
authored
Merge branch 'main' into marc/v3
2 parents da98bae + b1a60d5 commit 88e5375

56 files changed

Lines changed: 1610 additions & 330 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1111
<CodeAnalysisRuleset>$(MSBuildThisFileDirectory)Shared.ruleset</CodeAnalysisRuleset>
1212
<MSBuildWarningsAsMessages>NETSDK1069</MSBuildWarningsAsMessages>
13-
<NoWarn>$(NoWarn);NU5105;NU1507;SER001;SER002;SER003;SER004;SER005</NoWarn>
13+
<NoWarn>$(NoWarn);NU5105;NU1507;SER001;SER002;SER003;SER004;SER005;SER006</NoWarn>
1414
<PackageReleaseNotes>https://stackexchange.github.io/StackExchange.Redis/ReleaseNotes</PackageReleaseNotes>
1515
<PackageProjectUrl>https://stackexchange.github.io/StackExchange.Redis/</PackageProjectUrl>
1616
<PackageLicenseExpression>MIT</PackageLicenseExpression>

docs/ReleaseNotes.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ Current package versions:
88

99
## Unreleased
1010

11-
- (none)
11+
- none
12+
13+
## 2.12.8
14+
15+
- Add [`GCRA`](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm) support (and remove experimental flag on `VSIM`) ([#3041 by @mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/3041))
16+
- Add `IServer.GetProductVariant` to detect the product variant and version of the connected server, and use that internally
17+
to enable multi-DB operations on Valkey clusters ([#3040 by @mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/3040))
18+
- Ignore cluster nodes with the `handshake` flag ([#3043 by @TimLovellSmith](https://github.com/StackExchange/StackExchange.Redis/pull/3043))
1219

1320
## 2.12.4
1421

docs/exp/SER006.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Redis 8.8 is currently in preview and may be subject to change.
2+
3+
New features in Redis 8.8:
4+
5+
- `GCRA` for rate-limiting
6+
7+
The corresponding library feature must also be considered subject to change:
8+
9+
1. Existing bindings may cease working correctly if the underlying server API changes.
10+
2. Changes to the server API may require changes to the library API, manifesting in either/both of build-time
11+
or run-time breaks.
12+
13+
While this seems *unlikely*, it must be considered a possibility. If you acknowledge this, you can suppress
14+
this warning by adding the following to your `csproj` file:
15+
16+
```xml
17+
<NoWarn>$(NoWarn);SER006</NoWarn>
18+
```
19+
20+
or more granularly / locally in C#:
21+
22+
``` c#
23+
#pragma warning disable SER006
24+
```

src/RESPite/Messages/RespReader.cs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,30 +1852,54 @@ public readonly decimal ReadDecimal()
18521852
}
18531853

18541854
/// <summary>
1855-
/// Read the current element as a <see cref="bool"/> value.
1855+
/// Try to read the current element as a <see cref="bool"/> value.
18561856
/// </summary>
1857-
public readonly bool ReadBoolean()
1857+
/// <param name="value">The parsed boolean value if successful.</param>
1858+
/// <returns>True if the value was successfully parsed; false otherwise.</returns>
1859+
public readonly bool TryReadBoolean(out bool value)
18581860
{
18591861
var span = Buffer(stackalloc byte[2]);
18601862
switch (span.Length)
18611863
{
18621864
case 1:
18631865
switch (span[0])
18641866
{
1865-
case (byte)'0' when Prefix == RespPrefix.Integer: return false;
1866-
case (byte)'1' when Prefix == RespPrefix.Integer: return true;
1867-
case (byte)'f' when Prefix == RespPrefix.Boolean: return false;
1868-
case (byte)'t' when Prefix == RespPrefix.Boolean: return true;
1867+
case (byte)'0' when Prefix == RespPrefix.Integer:
1868+
value = false;
1869+
return true;
1870+
case (byte)'1' when Prefix == RespPrefix.Integer:
1871+
value = true;
1872+
return true;
1873+
case (byte)'f' when Prefix == RespPrefix.Boolean:
1874+
value = false;
1875+
return true;
1876+
case (byte)'t' when Prefix == RespPrefix.Boolean:
1877+
value = true;
1878+
return true;
18691879
}
18701880

18711881
break;
1872-
case 2 when Prefix == RespPrefix.SimpleString && IsOK(): return true;
1882+
case 2 when Prefix == RespPrefix.SimpleString && IsOK():
1883+
value = true;
1884+
return true;
18731885
}
18741886

1875-
ThrowFormatException();
1887+
value = false;
18761888
return false;
18771889
}
18781890

1891+
/// <summary>
1892+
/// Read the current element as a <see cref="bool"/> value.
1893+
/// </summary>
1894+
public readonly bool ReadBoolean()
1895+
{
1896+
if (!TryReadBoolean(out var value))
1897+
{
1898+
ThrowFormatException();
1899+
}
1900+
return value;
1901+
}
1902+
18791903
/// <summary>
18801904
/// Parse a scalar value as an enum of type <typeparamref name="T"/>.
18811905
/// </summary>

src/RESPite/PublicAPI/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
[SER004]RESPite.Messages.RespReader.ProtocolBytesRemaining.get -> long
156156
[SER004]RESPite.Messages.RespReader.ReadArray<TResult>(RESPite.Messages.RespReader.Projection<TResult>! projection, bool scalar = false) -> TResult[]?
157157
[SER004]RESPite.Messages.RespReader.ReadBoolean() -> bool
158+
[SER004]RESPite.Messages.RespReader.TryReadBoolean(out bool value) -> bool
158159
[SER004]RESPite.Messages.RespReader.ReadByteArray() -> byte[]?
159160
[SER004]RESPite.Messages.RespReader.ReadDecimal() -> decimal
160161
[SER004]RESPite.Messages.RespReader.ReadDouble() -> double

src/RESPite/Shared/Experiments.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ internal static class Experiments
88
public const string UrlFormat = "https://stackexchange.github.io/StackExchange.Redis/exp/";
99

1010
// ReSharper disable InconsistentNaming
11-
public const string VectorSets = "SER001";
1211
public const string Server_8_4 = "SER002";
1312
public const string Server_8_6 = "SER003";
1413
public const string Respite = "SER004";
1514
public const string UnitTesting = "SER005";
15+
public const string Server_8_8 = "SER006";
1616
// ReSharper restore InconsistentNaming
1717
}
1818
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using RESPite;
3+
4+
namespace StackExchange.Redis;
5+
6+
/// <summary>
7+
/// Fields that can appear in INFO output during auto-configuration.
8+
/// </summary>
9+
internal enum AutoConfigureInfoField
10+
{
11+
/// <summary>
12+
/// Unknown or unrecognized field.
13+
/// </summary>
14+
[AsciiHash("")]
15+
Unknown = 0,
16+
17+
[AsciiHash("role")]
18+
Role,
19+
20+
[AsciiHash("master_host")]
21+
MasterHost,
22+
23+
[AsciiHash("master_port")]
24+
MasterPort,
25+
26+
[AsciiHash("redis_version")]
27+
RedisVersion,
28+
29+
[AsciiHash("redis_mode")]
30+
RedisMode,
31+
32+
[AsciiHash("run_id")]
33+
RunId,
34+
35+
[AsciiHash("garnet_version")]
36+
GarnetVersion,
37+
38+
[AsciiHash("valkey_version")]
39+
ValkeyVersion,
40+
}
41+
42+
/// <summary>
43+
/// Metadata and parsing methods for <see cref="AutoConfigureInfoField"/>.
44+
/// </summary>
45+
internal static partial class AutoConfigureInfoFieldMetadata
46+
{
47+
[AsciiHash]
48+
internal static partial bool TryParse(ReadOnlySpan<char> value, out AutoConfigureInfoField field);
49+
}

src/StackExchange.Redis/ClusterConfiguration.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ internal ClusterNode(ClusterConfiguration configuration, string raw, EndPoint or
308308
}
309309

310310
NodeId = parts[0];
311+
IsHandshake = flags.Contains("handshake");
311312
IsFail = flags.Contains("fail");
312313
IsPossiblyFail = flags.Contains("fail?");
313314
IsReplica = flags.Contains("slave") || flags.Contains("replica");
@@ -377,6 +378,12 @@ public IList<ClusterNode> Children
377378
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
378379
public bool IsSlave => IsReplica;
379380

381+
/// <summary>
382+
/// The handshake flag is set for nodes which are currently in the process of joining the cluster.
383+
/// They might not be fully configured, node IDs and slot ranges are placeholder information, and endpoint details 'best guess'.
384+
/// </summary>
385+
public bool IsHandshake { get; }
386+
380387
/// <summary>
381388
/// Gets whether this node is a replica.
382389
/// </summary>
@@ -417,6 +424,10 @@ public IList<ClusterNode> Children
417424
/// </summary>
418425
public IList<SlotRange> Slots { get; }
419426

427+
// Be resilient to "handshake" nodes, which are nodes that are in the process of joining the cluster and hence might not have all information available yet.
428+
// These nodes will be included in the configuration once they finish the handshake process and are fully part of the cluster, so we can safely ignore them for now.
429+
internal bool IgnoreFromClient => IsHandshake; // possibly also noaddr?
430+
420431
/// <summary>
421432
/// Compares the current instance with another object of the same type and returns an integer that indicates
422433
/// whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.

src/StackExchange.Redis/ConnectionMultiplexer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,7 +1759,7 @@ public EndPoint[] GetEndPoints(bool configuredOnly = false) =>
17591759
{
17601760
return null;
17611761
}
1762-
var clusterEndpoints = new EndPointCollection(clusterConfig.Nodes.Where(node => node.EndPoint is not null).Select(node => node.EndPoint!).ToList());
1762+
var clusterEndpoints = new EndPointCollection(clusterConfig.Nodes.Where(node => node.EndPoint is not null && !node.IgnoreFromClient).Select(node => node.EndPoint!).ToList());
17631763
// Loop through nodes in the cluster and update nodes relations to other nodes
17641764
ServerEndPoint? serverEndpoint = null;
17651765
foreach (EndPoint endpoint in clusterEndpoints)
@@ -1932,7 +1932,7 @@ internal void UpdateClusterRange(ClusterConfiguration configuration)
19321932
}
19331933
foreach (var node in configuration.Nodes)
19341934
{
1935-
if (node.IsReplica || node.Slots.Count == 0) continue;
1935+
if (node.IgnoreFromClient || node.IsReplica || node.Slots.Count == 0) continue;
19361936
foreach (var slot in node.Slots)
19371937
{
19381938
if (GetServerEndPoint(node.EndPoint) is ServerEndPoint server)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// ReSharper disable once CheckNamespace
2+
namespace StackExchange.Redis;
3+
4+
/// <summary>
5+
/// Indicates the flavor of RESP-server being used, if known. Unknown variants will be reported as <see cref="Redis"/>.
6+
/// Inclusion (or omission) in this list does not imply support for any given variant; nor does it indicate any specific
7+
/// relationship with the vendor or rights to use the name. It is provided solely for informational purposes. Identification
8+
/// is not guaranteed, and is based on the server's self-reporting (typically via `INFO`), which may be incomplete or misleading.
9+
/// </summary>
10+
public enum ProductVariant
11+
{
12+
/// <summary>
13+
/// The original Redis server. This is also the default value if the variant is unknown.
14+
/// </summary>
15+
Redis,
16+
17+
/// <summary>
18+
/// <a href="https://valkey.io/">Valkey</a> is a fork of open-source Redis associated with AWS.
19+
/// </summary>
20+
Valkey,
21+
22+
/// <summary>
23+
/// <a href="https://microsoft.github.io/garnet/">Garnet</a> is a Redis-compatible server from Microsoft.
24+
/// </summary>
25+
Garnet,
26+
27+
// if you want to add another variant here, please open an issue with the details (variant name, INFO output, etc.)
28+
}

0 commit comments

Comments
 (0)