Skip to content

Commit f8495d8

Browse files
committed
Merge remote-tracking branch 'origin/marc/aa2' into marc/aa2
2 parents 7c4ad1e + c2b02fb commit f8495d8

15 files changed

Lines changed: 705 additions & 125 deletions

src/StackExchange.Redis/ConnectionMultiplexer.Debug.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@ namespace StackExchange.Redis;
44

55
public partial class ConnectionMultiplexer
66
{
7-
private static int _collectedWithoutDispose;
7+
private static int _collectedWithoutDispose, s_DisposedCount, s_MuxerCreateCount;
88
internal static int CollectedWithoutDispose => Volatile.Read(ref _collectedWithoutDispose);
99

10+
internal static int GetLiveObjectCount(out int created, out int disposed, out int finalized)
11+
{
12+
// read destroy first, to prevent negative numbers in race conditions
13+
disposed = Volatile.Read(ref s_DisposedCount);
14+
created = Volatile.Read(ref s_MuxerCreateCount);
15+
finalized = Volatile.Read(ref _collectedWithoutDispose);
16+
return created - (disposed + finalized);
17+
}
18+
1019
/// <summary>
1120
/// Invoked by the garbage collector.
1221
/// </summary>

src/StackExchange.Redis/ConnectionMultiplexer.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ static ConnectionMultiplexer()
128128

129129
private ConnectionMultiplexer(ConfigurationOptions configuration, ServerType? serverType = null, EndPointCollection? endpoints = null)
130130
{
131+
Interlocked.Increment(ref s_MuxerCreateCount);
132+
131133
RawConfig = configuration ?? throw new ArgumentNullException(nameof(configuration));
132134
EndPoints = endpoints ?? RawConfig.EndPoints.Clone();
133135
EndPoints.SetDefaultPorts(serverType, ssl: RawConfig.Ssl);
@@ -2260,7 +2262,8 @@ ConfigurationChangedChannel is byte[] channel
22602262
public void Dispose()
22612263
{
22622264
GC.SuppressFinalize(this);
2263-
Close(!_isDisposed);
2265+
if (!_isDisposed) Interlocked.Increment(ref s_DisposedCount);
2266+
Close(!_isDisposed); // marks disposed
22642267
sentinelConnection?.Dispose();
22652268
var oldTimer = Interlocked.Exchange(ref sentinelPrimaryReconnectTimer, null);
22662269
oldTimer?.Dispose();
@@ -2272,7 +2275,8 @@ public void Dispose()
22722275
public async ValueTask DisposeAsync()
22732276
{
22742277
GC.SuppressFinalize(this);
2275-
await CloseAsync(!_isDisposed).ForAwait();
2278+
if (!_isDisposed) Interlocked.Increment(ref s_DisposedCount);
2279+
await CloseAsync(!_isDisposed).ForAwait(); // marks disposed
22762280
if (sentinelConnection is ConnectionMultiplexer sentinel)
22772281
{
22782282
await sentinel.DisposeAsync().ForAwait();

src/StackExchange.Redis/ExceptionFactory.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,12 @@ private static void AddCommonDetail(
347347
Add(data, sb, "Last-Result-Bytes", "last-in", bs.Connection.BytesLastResult.ToString());
348348
Add(data, sb, "Inbound-Buffer-Bytes", "cur-in", bs.Connection.BytesInBuffer.ToString());
349349

350+
var liveMuxers = ConnectionMultiplexer.GetLiveObjectCount(out var created, out var disposed, out var finalized);
351+
if (created > 1)
352+
{
353+
Add(data, sb, "Live-Multiplexers", "lm", $"{liveMuxers}/{created}/{disposed}/{finalized}");
354+
}
355+
350356
Add(data, sb, "Sync-Ops", "sync-ops", multiplexer.syncOps.ToString());
351357
Add(data, sb, "Async-Ops", "async-ops", multiplexer.asyncOps.ToString());
352358

src/StackExchange.Redis/PhysicalBridge.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1622,8 +1622,13 @@ private WriteResult WriteMessageToServerInsideWriteLock(PhysicalConnection conne
16221622

16231623
if (_nextHighIntegrityToken is not 0
16241624
&& !connection.TransactionActive // validated in the UNWATCH/EXEC/DISCARD
1625-
&& message.Command is not (RedisCommand.AUTH or RedisCommand.HELLO)) // if auth fails, ECHO may also fail; avoid confusion
1625+
&& message.Command is not RedisCommand.AUTH // if auth fails, later commands may also fail; avoid confusion
1626+
&& message.Command is not RedisCommand.HELLO)
16261627
{
1628+
// note on the Command match above: curiously, .NET 10 and .NET 11 SDKs emit *opposite* errors here
1629+
// re "CS9336: The pattern is redundant." ("fixing" one "breaks" the other); possibly a fixed bool inversion
1630+
// in the analyzer? to avoid pain, we'll just use the most obviously correct form
1631+
16271632
// make sure this value exists early to avoid a race condition
16281633
// if the response comes back super quickly
16291634
message.WithHighIntegrity(NextHighIntegrityTokenInsideLock());

tests/StackExchange.Redis.Tests/BasicOpTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ public class BasicOpsTests(ITestOutputHelper output, SharedConnectionFixture fix
1212
{
1313
}
1414

15+
/*
1516
[RunPerProtocol]
1617
public class InProcBasicOpsTests(ITestOutputHelper output, InProcServerFixture fixture)
1718
: BasicOpsTestsBase(output, null, fixture)
1819
{
1920
}
21+
*/
2022

2123
[RunPerProtocol]
2224
public abstract class BasicOpsTestsBase(ITestOutputHelper output, SharedConnectionFixture? connection, InProcServerFixture? server)

tests/StackExchange.Redis.Tests/HighIntegrityBasicOpsTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ public class HighIntegrityBasicOpsTests(ITestOutputHelper output, SharedConnecti
77
internal override bool HighIntegrity => true;
88
}
99

10+
/*
1011
public class InProcHighIntegrityBasicOpsTests(ITestOutputHelper output, InProcServerFixture fixture) : InProcBasicOpsTests(output, fixture)
1112
{
1213
internal override bool HighIntegrity => true;
1314
}
15+
*/

tests/StackExchange.Redis.Tests/InProcessTestServer.cs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,14 @@ public InProcessTestServer(ITestOutputHelper? log = null, EndPoint? endpoint = n
2525
Tunnel = new InProcTunnel(this);
2626
}
2727

28-
public Task<ConnectionMultiplexer> ConnectAsync(bool withPubSub = false, TextWriter? log = null)
29-
=> ConnectionMultiplexer.ConnectAsync(GetClientConfig(withPubSub), log);
28+
public Task<ConnectionMultiplexer> ConnectAsync(TextWriter? log = null)
29+
=> ConnectionMultiplexer.ConnectAsync(GetClientConfig(), log);
3030

31-
public ConfigurationOptions GetClientConfig(bool withPubSub = false)
31+
public ConfigurationOptions GetClientConfig()
3232
{
3333
var commands = GetCommands();
34-
if (!withPubSub)
35-
{
36-
commands.Remove(nameof(RedisCommand.SUBSCRIBE));
37-
commands.Remove(nameof(RedisCommand.PSUBSCRIBE));
38-
commands.Remove(nameof(RedisCommand.SSUBSCRIBE));
39-
commands.Remove(nameof(RedisCommand.UNSUBSCRIBE));
40-
commands.Remove(nameof(RedisCommand.PUNSUBSCRIBE));
41-
commands.Remove(nameof(RedisCommand.SUNSUBSCRIBE));
42-
commands.Remove(nameof(RedisCommand.PUBLISH));
43-
commands.Remove(nameof(RedisCommand.SPUBLISH));
44-
}
45-
// transactions don't work yet
34+
35+
// transactions don't work yet (needs v3 buffer features)
4636
commands.Remove(nameof(RedisCommand.MULTI));
4737
commands.Remove(nameof(RedisCommand.EXEC));
4838
commands.Remove(nameof(RedisCommand.DISCARD));
@@ -82,6 +72,22 @@ protected override void OnMoved(RedisClient client, int hashSlot, Node node)
8272
base.OnMoved(client, hashSlot, node);
8373
}
8474

75+
protected override void OnOutOfBand(RedisClient client, TypedRedisValue message)
76+
{
77+
if (message.IsAggregate
78+
&& message.Span is { IsEmpty: false } span
79+
&& !span[0].IsAggregate)
80+
{
81+
_log?.WriteLine($"Client {client.Id}: {span[0].AsRedisValue()} {message} ");
82+
}
83+
else
84+
{
85+
_log?.WriteLine($"Client {client.Id}: {message}");
86+
}
87+
88+
base.OnOutOfBand(client, message);
89+
}
90+
8591
public override TypedRedisValue OnUnknownCommand(in RedisClient client, in RedisRequest request, ReadOnlySpan<byte> command)
8692
{
8793
_log?.WriteLine($"[{client.Id}] unknown command: {Encoding.ASCII.GetString(command)}");

tests/StackExchange.Redis.Tests/PubSubTests.cs

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,31 @@
1111
namespace StackExchange.Redis.Tests;
1212

1313
[RunPerProtocol]
14-
public class PubSubTests(ITestOutputHelper output, SharedConnectionFixture fixture) : TestBase(output, fixture)
14+
public class PubSubTests(ITestOutputHelper output, SharedConnectionFixture fixture)
15+
: PubSubTestBase(output, fixture, null)
16+
{
17+
}
18+
19+
/*
20+
[RunPerProtocol]
21+
public class InProcPubSubTests(ITestOutputHelper output, InProcServerFixture fixture)
22+
: PubSubTestBase(output, null, fixture)
23+
{
24+
protected override bool UseDedicatedInProcessServer => false;
25+
}
26+
*/
27+
28+
[RunPerProtocol]
29+
public abstract class PubSubTestBase(
30+
ITestOutputHelper output,
31+
SharedConnectionFixture? connection,
32+
InProcServerFixture? server)
33+
: TestBase(output, connection, server)
1534
{
1635
[Fact]
1736
public async Task ExplicitPublishMode()
1837
{
19-
await using var conn = Create(channelPrefix: "foo:", log: Writer);
38+
await using var conn = ConnectFactory(channelPrefix: "foo:");
2039

2140
var pub = conn.GetSubscriber();
2241
int a = 0, b = 0, c = 0, d = 0;
@@ -54,9 +73,9 @@ await UntilConditionAsync(
5473
[InlineData("Foo:", true, "f")]
5574
public async Task TestBasicPubSub(string? channelPrefix, bool wildCard, string breaker)
5675
{
57-
await using var conn = Create(channelPrefix: channelPrefix, shared: false, log: Writer);
76+
await using var conn = ConnectFactory(channelPrefix: channelPrefix, shared: false);
5877

59-
var pub = GetAnyPrimary(conn);
78+
var pub = GetAnyPrimary(conn.DefaultClient);
6079
var sub = conn.GetSubscriber();
6180
await PingAsync(pub, sub).ForAwait();
6281
HashSet<string?> received = [];
@@ -139,10 +158,10 @@ public async Task TestBasicPubSub(string? channelPrefix, bool wildCard, string b
139158
[Fact]
140159
public async Task TestBasicPubSubFireAndForget()
141160
{
142-
await using var conn = Create(shared: false, log: Writer);
161+
await using var conn = ConnectFactory(shared: false);
143162

144-
var profiler = conn.AddProfiler();
145-
var pub = GetAnyPrimary(conn);
163+
var profiler = conn.DefaultClient.AddProfiler();
164+
var pub = GetAnyPrimary(conn.DefaultClient);
146165
var sub = conn.GetSubscriber();
147166

148167
RedisChannel key = RedisChannel.Literal(Me() + Guid.NewGuid());
@@ -214,9 +233,9 @@ private async Task PingAsync(IServer pub, ISubscriber sub, int times = 1)
214233
[Fact]
215234
public async Task TestPatternPubSub()
216235
{
217-
await using var conn = Create(shared: false, log: Writer);
236+
await using var conn = ConnectFactory(shared: false);
218237

219-
var pub = GetAnyPrimary(conn);
238+
var pub = GetAnyPrimary(conn.DefaultClient);
220239
var sub = conn.GetSubscriber();
221240

222241
HashSet<string?> received = [];
@@ -273,7 +292,7 @@ public async Task TestPatternPubSub()
273292
[Fact]
274293
public async Task TestPublishWithNoSubscribers()
275294
{
276-
await using var conn = Create();
295+
await using var conn = ConnectFactory();
277296

278297
var sub = conn.GetSubscriber();
279298
#pragma warning disable CS0618
@@ -285,7 +304,7 @@ public async Task TestPublishWithNoSubscribers()
285304
public async Task TestMassivePublishWithWithoutFlush_Local()
286305
{
287306
Skip.UnlessLongRunning();
288-
await using var conn = Create();
307+
await using var conn = ConnectFactory();
289308

290309
var sub = conn.GetSubscriber();
291310
TestMassivePublish(sub, Me(), "local");
@@ -335,7 +354,7 @@ private void TestMassivePublish(ISubscriber sub, string channel, string caption)
335354
[Fact]
336355
public async Task SubscribeAsyncEnumerable()
337356
{
338-
await using var conn = Create(syncTimeout: 20000, shared: false, log: Writer);
357+
await using var conn = ConnectFactory(shared: false);
339358

340359
var sub = conn.GetSubscriber();
341360
RedisChannel channel = RedisChannel.Literal(Me());
@@ -370,7 +389,7 @@ public async Task SubscribeAsyncEnumerable()
370389
[Fact]
371390
public async Task PubSubGetAllAnyOrder()
372391
{
373-
await using var conn = Create(syncTimeout: 20000, shared: false, log: Writer);
392+
await using var conn = ConnectFactory(shared: false);
374393

375394
var sub = conn.GetSubscriber();
376395
RedisChannel channel = RedisChannel.Literal(Me());
@@ -625,9 +644,10 @@ public async Task PubSubGetAllCorrectOrder_OnMessage_Async()
625644
[Fact]
626645
public async Task TestPublishWithSubscribers()
627646
{
628-
await using var connA = Create(shared: false, log: Writer);
629-
await using var connB = Create(shared: false, log: Writer);
630-
await using var connPub = Create();
647+
await using var pair = ConnectFactory(shared: false);
648+
await using var connA = pair.DefaultClient;
649+
await using var connB = pair.CreateClient();
650+
await using var connPub = pair.CreateClient();
631651

632652
var channel = Me();
633653
var listenA = connA.GetSubscriber();
@@ -652,9 +672,10 @@ public async Task TestPublishWithSubscribers()
652672
[Fact]
653673
public async Task TestMultipleSubscribersGetMessage()
654674
{
655-
await using var connA = Create(shared: false, log: Writer);
656-
await using var connB = Create(shared: false, log: Writer);
657-
await using var connPub = Create();
675+
await using var pair = ConnectFactory(shared: false);
676+
await using var connA = pair.DefaultClient;
677+
await using var connB = pair.CreateClient();
678+
await using var connPub = pair.CreateClient();
658679

659680
var channel = RedisChannel.Literal(Me());
660681
var listenA = connA.GetSubscriber();
@@ -682,7 +703,7 @@ public async Task TestMultipleSubscribersGetMessage()
682703
[Fact]
683704
public async Task Issue38()
684705
{
685-
await using var conn = Create(log: Writer);
706+
await using var conn = ConnectFactory();
686707

687708
var sub = conn.GetSubscriber();
688709
int count = 0;
@@ -717,9 +738,10 @@ public async Task Issue38()
717738
[Fact]
718739
public async Task TestPartialSubscriberGetMessage()
719740
{
720-
await using var connA = Create();
721-
await using var connB = Create();
722-
await using var connPub = Create();
741+
await using var pair = ConnectFactory();
742+
await using var connA = pair.DefaultClient;
743+
await using var connB = pair.CreateClient();
744+
await using var connPub = pair.CreateClient();
723745

724746
int gotA = 0, gotB = 0;
725747
var listenA = connA.GetSubscriber();
@@ -750,8 +772,9 @@ public async Task TestPartialSubscriberGetMessage()
750772
[Fact]
751773
public async Task TestSubscribeUnsubscribeAndSubscribeAgain()
752774
{
753-
await using var connPub = Create();
754-
await using var connSub = Create();
775+
await using var pair = ConnectFactory();
776+
await using var connPub = pair.DefaultClient;
777+
await using var connSub = pair.CreateClient();
755778

756779
var prefix = Me();
757780
var pub = connPub.GetSubscriber();

0 commit comments

Comments
 (0)