Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ From 3.0, [release notes will be maintained in GitHub only](https://github.com/S
---


## 2.12.14

- (none)

## 2.13.17

- Fix TCP platform-dependent TCP keep-alive problems, and make an explicit option. ([#3090 by @mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/3090))
Expand Down
10 changes: 10 additions & 0 deletions src/RESPite/PublicAPI/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,13 @@
[SER004]virtual RESPite.Messages.RespReader.Projection<TState, TResult>.Invoke(ref TState state, ref RESPite.Messages.RespReader value) -> TResult
[SER004]virtual RESPite.Messages.RespReader.ScalarParser<TSource, TValue>.Invoke(scoped System.ReadOnlySpan<TSource> value, out TValue result) -> bool
[SER004]RESPite.Messages.RespReader.Serialize() -> byte[]!
[SER004]RESPite.Messages.RespReader.TryParseScalar<T>(RESPite.Messages.RespReader.ScalarParser<char, T>! parser, out T value) -> bool
[SER004]static RESPite.AsciiHash.EqualsCI(System.ReadOnlySpan<byte> first, System.ReadOnlySpan<char> second) -> bool
[SER004]static RESPite.AsciiHash.EqualsCI(System.ReadOnlySpan<char> first, System.ReadOnlySpan<byte> second) -> bool
[SER004]static RESPite.AsciiHash.SequenceEqualsCI(System.ReadOnlySpan<byte> first, System.ReadOnlySpan<char> second) -> bool
[SER004]static RESPite.AsciiHash.SequenceEqualsCI(System.ReadOnlySpan<char> first, System.ReadOnlySpan<byte> second) -> bool
[SER004]virtual RESPite.Buffers.CycleBufferPool<T>.Rent(in System.Buffers.ReadOnlySequence<T> existing) -> System.Buffers.IMemoryOwner<T>!
[SER004]RESPite.Buffers.CycleBuffer.Pool.get -> System.Buffers.MemoryPool<byte>!
[SER004]RESPite.Buffers.CycleBufferPool<T>
[SER004]RESPite.Buffers.CycleBufferPool<T>.CycleBufferPool() -> void
[SER004]static RESPite.Buffers.CycleBuffer.Create(System.Buffers.MemoryPool<byte>? pool = null, RESPite.Buffers.ICycleBufferCallback? callback = null) -> RESPite.Buffers.CycleBuffer
12 changes: 1 addition & 11 deletions src/RESPite/PublicAPI/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1 @@
#nullable enable
[SER004]RESPite.Messages.RespReader.TryParseScalar<T>(RESPite.Messages.RespReader.ScalarParser<char, T>! parser, out T value) -> bool
[SER004]static RESPite.AsciiHash.EqualsCI(System.ReadOnlySpan<byte> first, System.ReadOnlySpan<char> second) -> bool
[SER004]static RESPite.AsciiHash.EqualsCI(System.ReadOnlySpan<char> first, System.ReadOnlySpan<byte> second) -> bool
[SER004]static RESPite.AsciiHash.SequenceEqualsCI(System.ReadOnlySpan<byte> first, System.ReadOnlySpan<char> second) -> bool
[SER004]static RESPite.AsciiHash.SequenceEqualsCI(System.ReadOnlySpan<char> first, System.ReadOnlySpan<byte> second) -> bool
[SER004]virtual RESPite.Buffers.CycleBufferPool<T>.Rent(in System.Buffers.ReadOnlySequence<T> existing) -> System.Buffers.IMemoryOwner<T>!
[SER004]RESPite.Buffers.CycleBuffer.Pool.get -> System.Buffers.MemoryPool<byte>!
[SER004]RESPite.Buffers.CycleBufferPool<T>
[SER004]RESPite.Buffers.CycleBufferPool<T>.CycleBufferPool() -> void
[SER004]static RESPite.Buffers.CycleBuffer.Create(System.Buffers.MemoryPool<byte>? pool = null, RESPite.Buffers.ICycleBufferCallback? callback = null) -> RESPite.Buffers.CycleBuffer
#nullable enable
23 changes: 23 additions & 0 deletions src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2517,3 +2517,26 @@ virtual StackExchange.Redis.Configuration.DefaultOptionsProvider.TcpKeepAlive.ge
[SER006]StackExchange.Redis.ArrayInfo.ArrayInfo(scoped System.ReadOnlySpan<System.Collections.Generic.KeyValuePair<string!, StackExchange.Redis.RedisValue>> values) -> void
[SER006]StackExchange.Redis.ArrayInfo.ToDictionary() -> System.Collections.Generic.Dictionary<string!, StackExchange.Redis.RedisValue>!
[SER006]StackExchange.Redis.ArrayInfo.this[string! key].get -> StackExchange.Redis.RedisValue
StackExchange.Redis.ConfigurationOptions.RequestBufferPool.get -> System.Buffers.MemoryPool<byte>?
StackExchange.Redis.ConfigurationOptions.RequestBufferPool.set -> void
StackExchange.Redis.ConfigurationOptions.ResponseBufferPool.get -> System.Buffers.MemoryPool<byte>?
StackExchange.Redis.ConfigurationOptions.ResponseBufferPool.set -> void
static StackExchange.Redis.Lease<T>.Create(int length, System.Buffers.MemoryPool<T>? pool, bool clear = true) -> StackExchange.Redis.Lease<T>!
[SER005]StackExchange.Redis.TestHarness
[SER005]StackExchange.Redis.TestHarness.BufferValidator
[SER005]StackExchange.Redis.TestHarness.ChannelPrefix.get -> StackExchange.Redis.RedisChannel
[SER005]StackExchange.Redis.TestHarness.CommandMap.get -> StackExchange.Redis.CommandMap!
[SER005]StackExchange.Redis.TestHarness.KeyPrefix.get -> StackExchange.Redis.RedisKey
[SER005]StackExchange.Redis.TestHarness.Read(System.ReadOnlySpan<byte> value) -> StackExchange.Redis.RedisResult!
[SER005]StackExchange.Redis.TestHarness.TestHarness(StackExchange.Redis.CommandMap? commandMap = null, StackExchange.Redis.RedisChannel channelPrefix = default(StackExchange.Redis.RedisChannel), StackExchange.Redis.RedisKey keyPrefix = default(StackExchange.Redis.RedisKey)) -> void
[SER005]StackExchange.Redis.TestHarness.ValidateResp(System.ReadOnlySpan<byte> expected, string! command, params System.Collections.Generic.ICollection<object!>! args) -> void
[SER005]StackExchange.Redis.TestHarness.ValidateResp(string! expected, string! command, params System.Collections.Generic.ICollection<object!>! args) -> void
[SER005]StackExchange.Redis.TestHarness.ValidateRouting(in StackExchange.Redis.RedisKey expected, params System.Collections.Generic.ICollection<object!>! args) -> void
[SER005]StackExchange.Redis.TestHarness.Write(System.Buffers.IBufferWriter<byte>! target, string! command, params System.Collections.Generic.ICollection<object!>! args) -> void
[SER005]StackExchange.Redis.TestHarness.Write(string! command, params System.Collections.Generic.ICollection<object!>! args) -> byte[]!
[SER005]static StackExchange.Redis.TestHarness.AssertEqual(System.ReadOnlySpan<byte> expected, System.ReadOnlySpan<byte> actual, System.Action<System.ReadOnlyMemory<byte>, System.ReadOnlyMemory<byte>>! handler) -> void
[SER005]static StackExchange.Redis.TestHarness.AssertEqual(string! expected, System.ReadOnlySpan<byte> actual, System.Action<string!, string!>! handler) -> void
[SER005]virtual StackExchange.Redis.TestHarness.BufferValidator.Invoke(scoped System.ReadOnlySpan<byte> buffer) -> void
[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(System.ReadOnlyMemory<byte> expected, System.ReadOnlyMemory<byte> actual) -> void
[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(in StackExchange.Redis.RedisKey expected, in StackExchange.Redis.RedisKey actual) -> void
[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(string! expected, string! actual) -> void
25 changes: 1 addition & 24 deletions src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,24 +1 @@
#nullable enable
StackExchange.Redis.ConfigurationOptions.RequestBufferPool.get -> System.Buffers.MemoryPool<byte>?
StackExchange.Redis.ConfigurationOptions.RequestBufferPool.set -> void
StackExchange.Redis.ConfigurationOptions.ResponseBufferPool.get -> System.Buffers.MemoryPool<byte>?
StackExchange.Redis.ConfigurationOptions.ResponseBufferPool.set -> void
static StackExchange.Redis.Lease<T>.Create(int length, System.Buffers.MemoryPool<T>? pool, bool clear = true) -> StackExchange.Redis.Lease<T>!
[SER005]StackExchange.Redis.TestHarness
[SER005]StackExchange.Redis.TestHarness.BufferValidator
[SER005]StackExchange.Redis.TestHarness.ChannelPrefix.get -> StackExchange.Redis.RedisChannel
[SER005]StackExchange.Redis.TestHarness.CommandMap.get -> StackExchange.Redis.CommandMap!
[SER005]StackExchange.Redis.TestHarness.KeyPrefix.get -> StackExchange.Redis.RedisKey
[SER005]StackExchange.Redis.TestHarness.Read(System.ReadOnlySpan<byte> value) -> StackExchange.Redis.RedisResult!
[SER005]StackExchange.Redis.TestHarness.TestHarness(StackExchange.Redis.CommandMap? commandMap = null, StackExchange.Redis.RedisChannel channelPrefix = default(StackExchange.Redis.RedisChannel), StackExchange.Redis.RedisKey keyPrefix = default(StackExchange.Redis.RedisKey)) -> void
[SER005]StackExchange.Redis.TestHarness.ValidateResp(System.ReadOnlySpan<byte> expected, string! command, params System.Collections.Generic.ICollection<object!>! args) -> void
[SER005]StackExchange.Redis.TestHarness.ValidateResp(string! expected, string! command, params System.Collections.Generic.ICollection<object!>! args) -> void
[SER005]StackExchange.Redis.TestHarness.ValidateRouting(in StackExchange.Redis.RedisKey expected, params System.Collections.Generic.ICollection<object!>! args) -> void
[SER005]StackExchange.Redis.TestHarness.Write(System.Buffers.IBufferWriter<byte>! target, string! command, params System.Collections.Generic.ICollection<object!>! args) -> void
[SER005]StackExchange.Redis.TestHarness.Write(string! command, params System.Collections.Generic.ICollection<object!>! args) -> byte[]!
[SER005]static StackExchange.Redis.TestHarness.AssertEqual(System.ReadOnlySpan<byte> expected, System.ReadOnlySpan<byte> actual, System.Action<System.ReadOnlyMemory<byte>, System.ReadOnlyMemory<byte>>! handler) -> void
[SER005]static StackExchange.Redis.TestHarness.AssertEqual(string! expected, System.ReadOnlySpan<byte> actual, System.Action<string!, string!>! handler) -> void
[SER005]virtual StackExchange.Redis.TestHarness.BufferValidator.Invoke(scoped System.ReadOnlySpan<byte> buffer) -> void
[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(System.ReadOnlyMemory<byte> expected, System.ReadOnlyMemory<byte> actual) -> void
[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(in StackExchange.Redis.RedisKey expected, in StackExchange.Redis.RedisKey actual) -> void
[SER005]virtual StackExchange.Redis.TestHarness.OnValidateFail(string! expected, string! actual) -> void
#nullable enable
1 change: 1 addition & 0 deletions tests/StackExchange.Redis.Tests/ClusterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public async Task TestIdentity()
[Fact]
public async Task IntentionalWrongServer()
{
SkipOnWindowsRelease();
static string? StringGet(IServer server, RedisKey key, CommandFlags flags = CommandFlags.None)
=> (string?)server.Execute(0, "GET", [key], flags);

Expand Down
18 changes: 16 additions & 2 deletions tests/StackExchange.Redis.Tests/KeyIdleAsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,35 @@ namespace StackExchange.Redis.Tests;
[RunPerProtocol]
public class KeyIdleAsyncTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture)
{
// Target the standalone secure server (6381) rather than the default primary (6379).
// OBJECT IDLETIME is reset via the value's LRU access clock, but Redis deliberately
// suppresses that update while a save/AOF/replication-sync child is active (copy-on-write
// avoidance). The default primary has a replica attached, and every replica full-sync forks
// such a child; if one overlaps a touch/read in these tests, the idle time isn't reset and the
// test flakes (observed on Windows CI). 6381 has no replica and no persistence, so no fork ever
// suppresses the reset and the behaviour is deterministic. Overriding GetConfiguration here also
// opts these tests out of the shared connection fixture, giving each a dedicated 6381 connection.
protected override string GetConfiguration()
=> TestConfig.Current.SecureServerAndPort + ",password=" + TestConfig.Current.SecurePassword;

[Fact]
public async Task IdleTimeAsync()
{
SkipOnWindowsRelease("WSL exacerbates replication time");
await using var conn = Create();

RedisKey key = Me();
var db = conn.GetDatabase();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.StringSet(key, "new value", flags: CommandFlags.FireAndForget);
var timer = Stopwatch.StartNew();
await Task.Delay(2000).ForAwait();
var idleTime = await db.KeyIdleTimeAsync(key).ForAwait();
Assert.True(idleTime > TimeSpan.Zero, "First check");
Assert.True(idleTime > TimeSpan.Zero, $"First check: {idleTime} should be > 0; elapsed: {timer.ElapsedMilliseconds}ms");

db.StringSet(key, "new value2", flags: CommandFlags.FireAndForget);
var idleTime2 = await db.KeyIdleTimeAsync(key).ForAwait();
Assert.True(idleTime2 < idleTime, "Second check");
Assert.True(idleTime2 < idleTime, $"Second check: {idleTime2} should be < {idleTime}; elapsed: {timer.ElapsedMilliseconds}ms");

db.KeyDelete(key);
var idleTime3 = await db.KeyIdleTimeAsync(key).ForAwait();
Expand All @@ -33,6 +46,7 @@ public async Task IdleTimeAsync()
[Fact]
public async Task TouchIdleTimeAsync()
{
SkipOnWindowsRelease("WSL exacerbates replication time");
await using var conn = Create(require: RedisFeatures.v3_2_1);

RedisKey key = Me();
Expand Down
26 changes: 21 additions & 5 deletions tests/StackExchange.Redis.Tests/KeyIdleTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Xunit;

Expand All @@ -7,22 +8,35 @@ namespace StackExchange.Redis.Tests;
[RunPerProtocol]
public class KeyIdleTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture)
{
// Target the standalone secure server (6381) rather than the default primary (6379).
// OBJECT IDLETIME is reset via the value's LRU access clock, but Redis deliberately
// suppresses that update while a save/AOF/replication-sync child is active (copy-on-write
// avoidance). The default primary has a replica attached, and every replica full-sync forks
// such a child; if one overlaps a touch/read in these tests, the idle time isn't reset and the
// test flakes (observed on Windows CI). 6381 has no replica and no persistence, so no fork ever
// suppresses the reset and the behaviour is deterministic. Overriding GetConfiguration here also
// opts these tests out of the shared connection fixture, giving each a dedicated 6381 connection.
protected override string GetConfiguration()
=> TestConfig.Current.SecureServerAndPort + ",password=" + TestConfig.Current.SecurePassword;

[Fact]
public async Task IdleTime()
{
SkipOnWindowsRelease("WSL exacerbates replication time");
await using var conn = Create();

RedisKey key = Me();
var db = conn.GetDatabase();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.StringSet(key, "new value", flags: CommandFlags.FireAndForget);
var timer = Stopwatch.StartNew();
await Task.Delay(2000).ForAwait();
var idleTime = db.KeyIdleTime(key);
Assert.True(idleTime > TimeSpan.Zero);
Assert.True(idleTime > TimeSpan.Zero, $"First check: {idleTime} should be > 0; elapsed: {timer.ElapsedMilliseconds}ms");

db.StringSet(key, "new value2", flags: CommandFlags.FireAndForget);
var idleTime2 = db.KeyIdleTime(key);
Assert.True(idleTime2 < idleTime);
Assert.True(idleTime2 < idleTime, $"Second check: {idleTime2} should be < {idleTime}; elapsed: {timer.ElapsedMilliseconds}ms");

db.KeyDelete(key);
var idleTime3 = db.KeyIdleTime(key);
Expand All @@ -32,18 +46,20 @@ public async Task IdleTime()
[Fact]
public async Task TouchIdleTime()
{
SkipOnWindowsRelease("WSL exacerbates replication time");
await using var conn = Create(require: RedisFeatures.v3_2_1);

RedisKey key = Me();
var db = conn.GetDatabase();
db.KeyDelete(key, CommandFlags.FireAndForget);
db.StringSet(key, "new value", flags: CommandFlags.FireAndForget);
var timer = Stopwatch.StartNew();
await Task.Delay(2000).ForAwait();
var idleTime = db.KeyIdleTime(key);
Assert.True(idleTime > TimeSpan.Zero, "First check");
Assert.True(idleTime > TimeSpan.Zero, $"First check: {idleTime} should be > 0; elapsed: {timer.ElapsedMilliseconds}ms");

Assert.True(db.KeyTouch(key), "Second check");
Assert.True(db.KeyTouch(key), $"Second check: should be True; elapsed: {timer.ElapsedMilliseconds}ms");
var idleTime1 = db.KeyIdleTime(key);
Assert.True(idleTime1 < idleTime, "Third check");
Assert.True(idleTime1 < idleTime, $"Third check: {idleTime1} should be < {idleTime}; elapsed: {timer.ElapsedMilliseconds}ms");
}
}
1 change: 1 addition & 0 deletions tests/StackExchange.Redis.Tests/RoleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class Roles(ITestOutputHelper output, SharedConnectionFixture fixture) :
[InlineData(false)]
public async Task PrimaryRole(bool allowAdmin) // should work with or without admin now
{
SkipOnWindowsRelease();
await using var conn = Create(allowAdmin: allowAdmin);
var servers = conn.GetServers();
Log("Server list:");
Expand Down
Loading
Loading