Skip to content

Commit 02c29ef

Browse files
authored
Counters: add sync-ops, async-ops, and how long a server has been connected (#2300)
More info to help us advise and debug timeouts for users! Overall adds for timeout messages: - `Sync-Ops`: A count of synchronous operation calls - `Async-Ops`: A count of asynchronous operation calls - `Server-Connected-Seconds`: How long the bridge in question has been connected ("n/a" if not connected)
1 parent 8476651 commit 02c29ef

11 files changed

Lines changed: 34 additions & 8 deletions

File tree

docs/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Current package versions:
1111
- Fix [#1520](https://github.com/StackExchange/StackExchange.Redis/issues/1520) & [#1660](https://github.com/StackExchange/StackExchange.Redis/issues/1660): When `MOVED` is encountered from a cluster, a reconfigure will happen proactively to react to cluster changes ASAP ([#2286 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2286))
1212
- Fix [#2249](https://github.com/StackExchange/StackExchange.Redis/issues/2249): Properly handle a `fail` state (new `ClusterNode.IsFail` property) for `CLUSTER NODES` and expose `fail?` as a property (`IsPossiblyFail`) as well ([#2288 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2288))
1313
- Adds: `IConnectionMultiplexer.ServerMaintenanceEvent` (was on `ConnectionMultiplexer` but not the interface) ([#2306 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2306))
14+
- Adds: To timeout messages, additional debug information: `Sync-Ops` (synchronous operations), `Async-Ops` (asynchronous operations), and `Server-Connected-Seconds` (how long the connection in question has been connected, or `"n/a"`) ([#2300 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2300))
1415

1516

1617
## 2.6.80

src/StackExchange.Redis/ConnectionMultiplexer.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public sealed partial class ConnectionMultiplexer : IInternalConnectionMultiplex
3131
/// Tracks overall connection multiplexer counts.
3232
/// </summary>
3333
internal int _connectAttemptCount = 0, _connectCompletedCount = 0, _connectionCloseCount = 0;
34+
internal long syncOps, asyncOps;
3435
private long syncTimeouts, fireAndForgets, asyncTimeouts;
3536
private string? failureMessage, activeConfigCause;
3637
private IDisposable? pulse;
@@ -1867,6 +1868,8 @@ internal static void ThrowFailed<T>(TaskCompletionSource<T>? source, Exception u
18671868
return defaultValue;
18681869
}
18691870

1871+
Interlocked.Increment(ref syncOps);
1872+
18701873
if (message.IsFireAndForget)
18711874
{
18721875
#pragma warning disable CS0618 // Type or member is obsolete
@@ -1929,6 +1932,8 @@ static async Task<T> ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value
19291932
return CompletedTask<T>.FromDefault(defaultValue, state);
19301933
}
19311934

1935+
Interlocked.Increment(ref asyncOps);
1936+
19321937
TaskCompletionSource<T>? tcs = null;
19331938
IResultBox<T>? source = null;
19341939
if (!message.IsFireAndForget)
@@ -1978,6 +1983,8 @@ static async Task<T> ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value
19781983
return CompletedTask<T?>.Default(state);
19791984
}
19801985

1986+
Interlocked.Increment(ref asyncOps);
1987+
19811988
TaskCompletionSource<T?>? tcs = null;
19821989
IResultBox<T?>? source = null;
19831990
if (!message.IsFireAndForget)

src/StackExchange.Redis/ExceptionFactory.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Reflection;
43
using System.Security.Authentication;
54
using System.Text;
65
using System.Threading;
@@ -323,13 +322,17 @@ private static void AddCommonDetail(
323322
Add(data, sb, "Last-Result-Bytes", "last-in", bs.Connection.BytesLastResult.ToString());
324323
Add(data, sb, "Inbound-Buffer-Bytes", "cur-in", bs.Connection.BytesInBuffer.ToString());
325324

325+
Add(data, sb, "Sync-Ops", "sync-ops", multiplexer.syncOps.ToString());
326+
Add(data, sb, "Async-Ops", "async-ops", multiplexer.asyncOps.ToString());
327+
326328
if (multiplexer.StormLogThreshold >= 0 && bs.Connection.MessagesSentAwaitingResponse >= multiplexer.StormLogThreshold && Interlocked.CompareExchange(ref multiplexer.haveStormLog, 1, 0) == 0)
327329
{
328330
var log = server.GetStormLog(message);
329331
if (string.IsNullOrWhiteSpace(log)) Interlocked.Exchange(ref multiplexer.haveStormLog, 0);
330332
else Interlocked.Exchange(ref multiplexer.stormLogSnapshot, log);
331333
}
332334
Add(data, sb, "Server-Endpoint", "serverEndpoint", (server.EndPoint.ToString() ?? "Unknown").Replace("Unspecified/", ""));
335+
Add(data, sb, "Server-Connected-Seconds", "conn-sec", bs.ConnectedAt is DateTime dt ? (DateTime.UtcNow - dt).TotalSeconds.ToString("0.##") : "n/a");
333336
}
334337
Add(data, sb, "Multiplexer-Connects", "mc", $"{multiplexer._connectAttemptCount}/{multiplexer._connectCompletedCount}/{multiplexer._connectionCloseCount}");
335338
Add(data, sb, "Manager", "mgr", multiplexer.SocketManager?.GetState());

src/StackExchange.Redis/PhysicalBridge.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ internal sealed class PhysicalBridge : IDisposable
6666
#endif
6767

6868
internal string? PhysicalName => physical?.ToString();
69+
public DateTime? ConnectedAt { get; private set; }
6970

7071
public PhysicalBridge(ServerEndPoint serverEndPoint, ConnectionType type, int timeoutMilliseconds)
7172
{
@@ -265,6 +266,10 @@ internal readonly struct BridgeStatus
265266
/// </summary>
266267
public int MessagesSinceLastHeartbeat { get; init; }
267268
/// <summary>
269+
/// The time this connection was connected at, if it's connected currently.
270+
/// </summary>
271+
public DateTime? ConnectedAt { get; init; }
272+
/// <summary>
268273
/// Whether the pipe writer is currently active.
269274
/// </summary>
270275
public bool IsWriterActive { get; init; }
@@ -298,12 +303,13 @@ internal readonly struct BridgeStatus
298303
public static BridgeStatus Zero { get; } = new() { Connection = PhysicalConnection.ConnectionStatus.Zero };
299304

300305
public override string ToString() =>
301-
$"MessagesSinceLastHeartbeat: {MessagesSinceLastHeartbeat}, Writer: {(IsWriterActive ? "Active" : "Inactive")}, BacklogStatus: {BacklogStatus}, BacklogMessagesPending: (Queue: {BacklogMessagesPending}, Counter: {BacklogMessagesPendingCounter}), TotalBacklogMessagesQueued: {TotalBacklogMessagesQueued}, Connection: ({Connection})";
306+
$"MessagesSinceLastHeartbeat: {MessagesSinceLastHeartbeat}, ConnectedAt: {ConnectedAt?.ToString("u") ?? "n/a"}, Writer: {(IsWriterActive ? "Active" : "Inactive")}, BacklogStatus: {BacklogStatus}, BacklogMessagesPending: (Queue: {BacklogMessagesPending}, Counter: {BacklogMessagesPendingCounter}), TotalBacklogMessagesQueued: {TotalBacklogMessagesQueued}, Connection: ({Connection})";
302307
}
303308

304309
internal BridgeStatus GetStatus() => new()
305310
{
306311
MessagesSinceLastHeartbeat = (int)(Interlocked.Read(ref operationCount) - Interlocked.Read(ref profileLastLog)),
312+
ConnectedAt = ConnectedAt,
307313
#if NETCOREAPP
308314
IsWriterActive = _singleWriterMutex.CurrentCount == 0,
309315
#else
@@ -385,6 +391,7 @@ internal async Task OnConnectedAsync(PhysicalConnection connection, LogProxy? lo
385391
Trace("OnConnected");
386392
if (physical == connection && !isDisposed && ChangeState(State.Connecting, State.ConnectedEstablishing))
387393
{
394+
ConnectedAt ??= DateTime.UtcNow;
388395
await ServerEndPoint.OnEstablishingAsync(connection, log).ForAwait();
389396
log?.WriteLine($"{Format.ToString(ServerEndPoint)}: OnEstablishingAsync complete");
390397
}
@@ -431,6 +438,7 @@ internal void OnDisconnected(ConnectionFailureType failureType, PhysicalConnecti
431438
Trace($"OnDisconnected: {failureType}");
432439

433440
oldState = default(State); // only defined when isCurrent = true
441+
ConnectedAt = default;
434442
if (isCurrent = (physical == connection))
435443
{
436444
Trace("Bridge noting disconnect from active connection" + (isDisposed ? " (disposed)" : ""));

tests/BasicTest/BasicTest.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Description>StackExchange.Redis.BasicTest .NET Core</Description>
5-
<TargetFrameworks>net472;net5.0</TargetFrameworks>
5+
<TargetFrameworks>net472;net6.0</TargetFrameworks>
66
<AssemblyName>BasicTest</AssemblyName>
77
<OutputType>Exe</OutputType>
88
<PackageId>BasicTest</PackageId>

tests/BasicTestBaseline/BasicTestBaseline.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Description>StackExchange.Redis.BasicTest .NET Core</Description>
5-
<TargetFrameworks>net472;net5.0</TargetFrameworks>
5+
<TargetFrameworks>net472;net6.0</TargetFrameworks>
66
<AssemblyName>BasicTestBaseline</AssemblyName>
77
<OutputType>Exe</OutputType>
88
<PackageId>BasicTestBaseline</PackageId>

tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ public void TimeoutException()
121121
Assert.Contains("serverEndpoint: " + server.EndPoint, ex.Message);
122122
Assert.Contains("IOCP: ", ex.Message);
123123
Assert.Contains("WORKER: ", ex.Message);
124+
Assert.Contains("sync-ops: ", ex.Message);
125+
Assert.Contains("async-ops: ", ex.Message);
126+
Assert.Contains("conn-sec: n/a", ex.Message);
124127
#if NETCOREAPP
125128
Assert.Contains("POOL: ", ex.Message);
126129
#endif

tests/StackExchange.Redis.Tests/SentinelTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,12 @@ public async Task SentinelGetSentinelAddressesTest()
451451
public async Task ReadOnlyConnectionReplicasTest()
452452
{
453453
var replicas = SentinelServerA.SentinelGetReplicaAddresses(ServiceName);
454-
var config = new ConfigurationOptions();
454+
if (replicas.Length == 0)
455+
{
456+
Skip.Inconclusive("Sentinel race: 0 replicas to test against.");
457+
}
455458

459+
var config = new ConfigurationOptions();
456460
foreach (var replica in replicas)
457461
{
458462
config.EndPoints.Add(replica);

toys/KestrelRedisServer/KestrelRedisServer.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>net5.0</TargetFramework>
4+
<TargetFramework>net6.0</TargetFramework>
55
<NoWarn>$(NoWarn);CS1591</NoWarn>
66
</PropertyGroup>
77

toys/TestConsole/TestConsole.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFrameworks>net50;net472</TargetFrameworks>
5+
<TargetFrameworks>net6.0;net472</TargetFrameworks>
66
<DefineConstants>SEV2</DefineConstants>
77
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
88
</PropertyGroup>

0 commit comments

Comments
 (0)