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
33 changes: 21 additions & 12 deletions tests/StackExchange.Redis.Tests/ClusterCommandsRefusedUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,29 @@ public async Task AllClusterCommandsRefusedIsQuiet()
config.LoggerFactory = logger;
await using var conn = await ConnectionMultiplexer.ConnectAsync(config);

log.WriteLine(logger.All);
Assert.Empty(logger.Matching("Encountered error while updating cluster config"));
Assert.NotEmpty(logger.Matching("CLUSTER NODES was refused"));
try
{
Assert.Empty(logger.Matching("Encountered error while updating cluster config"));
Assert.NotEmpty(logger.Matching("CLUSTER NODES was refused"));

await conn.GetDatabase().PingAsync();
await conn.GetDatabase().PingAsync();

// ...and a later explicit reconfigure stays quiet too
Assert.True(await conn.ReconfigureAsync("test"));
Assert.Empty(logger.Matching("Encountered error while updating cluster config"));
// ...and a later explicit reconfigure stays quiet too; the connect's own follow-on reconfigures
// (connection restored, autoconfigure changes) may still be in flight, and ReconfigureAsync declines
// while one is, so insist on a pass that actually ran
await Poll.ReconfigureAsync(conn, "test");
Assert.Empty(logger.Matching("Encountered error while updating cluster config"));

// with no slot map there is nothing to route by, so we must not *route* as a cluster - in particular,
// cross-slot commands go to the proxy rather than being refused client-side (#3284)
Assert.NotEmpty(logger.Matching("no slot map is available; routing as standalone"));
Assert.Equal(ServerType.Standalone, conn.ServerSelectionStrategy.ServerType);
await conn.GetDatabase().StringGetAsync(["a", "b"]);
// with no slot map there is nothing to route by, so we must not *route* as a cluster - in particular,
// cross-slot commands go to the proxy rather than being refused client-side (#3284)
Assert.NotEmpty(logger.Matching("no slot map is available; routing as standalone"));
Assert.Equal(ServerType.Standalone, conn.ServerSelectionStrategy.ServerType);
await conn.GetDatabase().StringGetAsync(["a", "b"]);
}
finally
{
// after the assertions, so a failure shows what the multiplexer actually logged
log.WriteLine(logger.All);
}
}
}
7 changes: 1 addition & 6 deletions tests/StackExchange.Redis.Tests/HelloClusterModeUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,7 @@ public async Task CrossSlotSurvivesSubscriptionReconnect(RedisProtocol protocol)

// the restored connection triggers a fire-and-forget reconfigure (and so can a server-type change);
// ReconfigureAsync declines while one is in flight, so insist on one that actually ran
var deadline = DateTime.UtcNow.AddSeconds(10);
while (!await conn.ReconfigureAsync("after subscription reconnect"))
{
if (DateTime.UtcNow > deadline) throw new TimeoutException("Timed out waiting for a reconfigure to run");
await Task.Delay(50);
}
await Poll.ReconfigureAsync(conn, "after subscription reconnect");

try
{
Expand Down
20 changes: 20 additions & 0 deletions tests/StackExchange.Redis.Tests/Helpers/Poll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,24 @@ public static async Task<bool> UntilAsync(Func<bool> predicate, int timeoutMilli

return false;
}

/// <summary>
/// Runs a reconfigure that is guaranteed to have actually executed, rather than been declined.
/// </summary>
/// <remarks>
/// A freshly connected multiplexer fires its own fire-and-forget reconfigures (connection-restored for each
/// physical connection, autoconfigure field changes), and <see cref="ConnectionMultiplexer.ReconfigureAsync(string)"/>
/// returns <c>false</c> while one is in flight. A test that asserts on that return value races those, and
/// loses on a slow runner; this keeps asking until a pass runs under the caller's cause.
/// </remarks>
public static async Task ReconfigureAsync(ConnectionMultiplexer conn, string cause, int timeoutMilliseconds = 10000, int pollMilliseconds = 50)
{
var cancellationToken = TestContext.Current.CancellationToken;
for (int i = 0; i <= timeoutMilliseconds / pollMilliseconds; i++)
{
if (await conn.ReconfigureAsync(cause).ConfigureAwait(false)) return;
await Task.Delay(pollMilliseconds, cancellationToken).ConfigureAwait(false);
}
throw new TimeoutException("Timed out waiting for a reconfigure to run: " + cause);
}
}
Loading