Skip to content

Commit 6104eb3

Browse files
author
Paul Cernuto
committed
Fix code style warnings and improve timing accuracy
Fix: Ensure ObserverCircuitBreaker.failureThreshold is at least 1 when circuit breaker is enabled (health tracker allows 0 to disable tracking entirely) Convert backing fields to auto properties (IDE0032): SignalRGroupCoordinatorGrain: GroupPartitions, GroupMembership ExpiringObserverBuffer: MessageCount ObserverCircuitBreaker: LastException ObserverHealthTracker: CircuitBreakerEnabled, LastException, IsDead Convert classes to primary constructors (IDE0290) Add editorconfig suppressions for SignalRConnectionHeartbeatGrain ([PersistentState] incompatible with primary constructors) Replace DateTime.UtcNow with Stopwatch.GetTimestamp() for elapsed time measurements in circuit breaker, health tracker, and grace period buffer to prevent timing issues from system clock adjustments
1 parent f420c71 commit 6104eb3

File tree

96 files changed

+420
-537
lines changed

Some content is hidden

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

96 files changed

+420
-537
lines changed

.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
"allow": [
44
"Bash(tree:*)",
55
"Bash(dotnet build:*)",
6-
"Bash(dotnet test:*)"
6+
"Bash(dotnet test:*)",
7+
"Bash(git checkout:*)",
8+
"Bash(git push:*)",
9+
"Bash(git remote add:*)"
710
]
811
}
912
}

.editorconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,20 @@ dotnet_diagnostic.IDE0200.severity = warning
453453
dotnet_style_allow_multiple_blank_lines_experimental = false
454454
dotnet_diagnostic.IDE2000.severity = warning
455455

456+
# SignalR Hub methods are called by string name from clients - suppress async naming rule
457+
[**/Hubs/*.cs]
458+
dotnet_naming_rule.async_methods_should_end_with_async.severity = none
459+
460+
# ASP.NET Controller actions don't typically follow async naming conventions
461+
[**/Controllers/*.cs]
462+
dotnet_naming_rule.async_methods_should_end_with_async.severity = none
463+
464+
# Orleans grains with [PersistentState] attributes cannot use primary constructors
465+
# because attributes on constructor parameters cannot be applied to primary constructor parameters
466+
[**/SignalRConnectionHeartbeatGrain.cs]
467+
csharp_style_prefer_primary_constructors = false:none
468+
dotnet_diagnostic.IDE0290.severity = none
469+
456470
# Verify settings for test files
457471
[*.{received,verified}.{txt,xml,json}]
458472
charset = utf-8-bom

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,4 +646,7 @@ MigrationBackup/
646646
# Ionide (cross platform F# VS Code tools) working folder
647647
.ionide/
648648

649-
# End of https://www.toptal.com/developers/gitignore/api/intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider
649+
# End of https://www.toptal.com/developers/gitignore/api/intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider
650+
651+
# Claude Code temporary directories
652+
tmpclaude-*/

ManagedCode.Orleans.SignalR.Core/Helpers/CollectionPool.cs

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ public static class CollectionPool
1212
{
1313
private const int MaxPoolSize = 256;
1414

15-
private static readonly ConcurrentBag<HashSet<string>> StringHashSetPool = new();
16-
private static readonly ConcurrentBag<List<string>> StringListPool = new();
17-
private static readonly ConcurrentBag<Dictionary<int, List<string>>> IntListDictionaryPool = new();
15+
private static readonly ConcurrentBag<HashSet<string>> _stringHashSetPool = new();
16+
private static readonly ConcurrentBag<List<string>> _stringListPool = new();
17+
private static readonly ConcurrentBag<Dictionary<int, List<string>>> _intListDictionaryPool = new();
1818

1919
/// <summary>
2020
/// Gets a HashSet&lt;string&gt; from the pool or creates a new one.
2121
/// </summary>
2222
public static HashSet<string> GetStringHashSet()
2323
{
24-
if (StringHashSetPool.TryTake(out var set))
24+
if (_stringHashSetPool.TryTake(out var set))
2525
{
2626
return set;
2727
}
@@ -34,21 +34,21 @@ public static HashSet<string> GetStringHashSet()
3434
/// </summary>
3535
public static void Return(HashSet<string> set)
3636
{
37-
if (set is null || StringHashSetPool.Count >= MaxPoolSize)
37+
if (set is null || _stringHashSetPool.Count >= MaxPoolSize)
3838
{
3939
return;
4040
}
4141

4242
set.Clear();
43-
StringHashSetPool.Add(set);
43+
_stringHashSetPool.Add(set);
4444
}
4545

4646
/// <summary>
4747
/// Gets a List&lt;string&gt; from the pool or creates a new one.
4848
/// </summary>
4949
public static List<string> GetStringList()
5050
{
51-
if (StringListPool.TryTake(out var list))
51+
if (_stringListPool.TryTake(out var list))
5252
{
5353
return list;
5454
}
@@ -61,7 +61,7 @@ public static List<string> GetStringList()
6161
/// </summary>
6262
public static List<string> GetStringList(int capacity)
6363
{
64-
if (StringListPool.TryTake(out var list))
64+
if (_stringListPool.TryTake(out var list))
6565
{
6666
if (list.Capacity < capacity)
6767
{
@@ -78,21 +78,21 @@ public static List<string> GetStringList(int capacity)
7878
/// </summary>
7979
public static void Return(List<string> list)
8080
{
81-
if (list is null || StringListPool.Count >= MaxPoolSize)
81+
if (list is null || _stringListPool.Count >= MaxPoolSize)
8282
{
8383
return;
8484
}
8585

8686
list.Clear();
87-
StringListPool.Add(list);
87+
_stringListPool.Add(list);
8888
}
8989

9090
/// <summary>
9191
/// Gets a Dictionary&lt;int, List&lt;string&gt;&gt; from the pool.
9292
/// </summary>
9393
public static Dictionary<int, List<string>> GetIntListDictionary()
9494
{
95-
if (IntListDictionaryPool.TryTake(out var dict))
95+
if (_intListDictionaryPool.TryTake(out var dict))
9696
{
9797
return dict;
9898
}
@@ -106,7 +106,7 @@ public static Dictionary<int, List<string>> GetIntListDictionary()
106106
/// </summary>
107107
public static void Return(Dictionary<int, List<string>> dict)
108108
{
109-
if (dict is null || IntListDictionaryPool.Count >= MaxPoolSize)
109+
if (dict is null || _intListDictionaryPool.Count >= MaxPoolSize)
110110
{
111111
return;
112112
}
@@ -118,20 +118,15 @@ public static void Return(Dictionary<int, List<string>> dict)
118118
}
119119

120120
dict.Clear();
121-
IntListDictionaryPool.Add(dict);
121+
_intListDictionaryPool.Add(dict);
122122
}
123123

124124
/// <summary>
125125
/// A scope that automatically returns a HashSet to the pool when disposed.
126126
/// </summary>
127-
public readonly struct HashSetScope : IDisposable
127+
public readonly struct HashSetScope(HashSet<string> set) : IDisposable
128128
{
129-
public HashSet<string> Set { get; }
130-
131-
public HashSetScope(HashSet<string> set)
132-
{
133-
Set = set;
134-
}
129+
public HashSet<string> Set { get; } = set;
135130

136131
public void Dispose()
137132
{
@@ -142,14 +137,9 @@ public void Dispose()
142137
/// <summary>
143138
/// A scope that automatically returns a List to the pool when disposed.
144139
/// </summary>
145-
public readonly struct ListScope : IDisposable
140+
public readonly struct ListScope(List<string> list) : IDisposable
146141
{
147-
public List<string> List { get; }
148-
149-
public ListScope(List<string> list)
150-
{
151-
List = list;
152-
}
142+
public List<string> List { get; } = list;
153143

154144
public void Dispose()
155145
{

ManagedCode.Orleans.SignalR.Core/Helpers/PartitionHelper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ public static class PartitionHelper
1515
{
1616
private const int VirtualNodesPerPartition = 150; // Number of virtual nodes per physical partition
1717
private const int MaxStackAllocSize = 256; // Max bytes for stackalloc
18-
private static readonly ConcurrentDictionary<RingCacheKey, ConsistentHashRing> RingCache = new();
18+
private static readonly ConcurrentDictionary<_ringCacheKey, ConsistentHashRing> _ringCache = new();
1919

2020
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2121
public static int GetPartitionId(string connectionId, uint partitionCount)
2222
{
2323
ArgumentException.ThrowIfNullOrEmpty(connectionId);
2424
ArgumentOutOfRangeException.ThrowIfZero(partitionCount);
2525

26-
var ring = RingCache.GetOrAdd(new RingCacheKey((int)partitionCount, VirtualNodesPerPartition),
26+
var ring = _ringCache.GetOrAdd(new _ringCacheKey((int)partitionCount, VirtualNodesPerPartition),
2727
static key => new ConsistentHashRing(key.PartitionCount, key.VirtualNodes));
2828

2929
return ring.GetPartition(connectionId);
@@ -84,7 +84,7 @@ internal static uint ComputeHash(ReadOnlySpan<char> key)
8484
}
8585
}
8686

87-
private readonly record struct RingCacheKey(int PartitionCount, int VirtualNodes);
87+
private readonly record struct _ringCacheKey(int PartitionCount, int VirtualNodes);
8888
}
8989

9090
public sealed class ConsistentHashRing

ManagedCode.Orleans.SignalR.Core/Helpers/RetryHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
using Orleans.Runtime;
21
using System;
32
using System.Threading;
43
using System.Threading.Tasks;
4+
using Orleans.Runtime;
55

66
namespace ManagedCode.Orleans.SignalR.Core.Helpers;
77

@@ -195,7 +195,7 @@ public RetryPolicy(int maxAttempts, TimeSpan initialDelay, TimeSpan maxDelay, do
195195
{
196196
MaxAttempts = Math.Max(1, maxAttempts);
197197
InitialDelay = initialDelay > TimeSpan.Zero ? initialDelay : TimeSpan.FromMilliseconds(100);
198-
MaxDelay = maxDelay > initialDelay ? maxDelay : TimeSpan.FromSeconds(30);
198+
MaxDelay = maxDelay > InitialDelay ? maxDelay : TimeSpan.FromSeconds(30);
199199
ExponentialBase = Math.Max(1.1, exponentialBase);
200200
}
201201

ManagedCode.Orleans.SignalR.Core/Helpers/TimeIntervalHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
using System;
2+
using System.Threading;
13
using ManagedCode.Orleans.SignalR.Core.Config;
24
using Microsoft.AspNetCore.SignalR;
35
using Microsoft.Extensions.Options;
4-
using System;
5-
using System.Threading;
66

77
namespace ManagedCode.Orleans.SignalR.Core.Helpers;
88

ManagedCode.Orleans.SignalR.Core/HubContext/OrleansHubClients.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using Microsoft.AspNetCore.SignalR;
21
using System.Collections.Generic;
2+
using Microsoft.AspNetCore.SignalR;
33

44
namespace ManagedCode.Orleans.SignalR.Core.HubContext;
55

ManagedCode.Orleans.SignalR.Core/HubContext/TypedClientBuilder.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
using Microsoft.AspNetCore.SignalR;
21
using System;
32
using System.Collections.Generic;
43
using System.Linq;
54
using System.Reflection;
65
using System.Reflection.Emit;
76
using System.Threading;
87
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.SignalR;
99

1010
namespace ManagedCode.Orleans.SignalR.Core.HubContext;
1111

@@ -16,12 +16,12 @@ internal static class TypedClientBuilder<T>
1616
// There is one static instance of _builder per T
1717
private static readonly Lazy<Func<IClientProxy, T>> _builder = new(GenerateClientBuilder);
1818

19-
private static readonly PropertyInfo CancellationTokenNoneProperty =
19+
private static readonly PropertyInfo _cancellationTokenNoneProperty =
2020
typeof(CancellationToken).GetProperty("None", BindingFlags.Public | BindingFlags.Static)!;
2121

22-
private static readonly ConstructorInfo ObjectConstructor = typeof(object).GetConstructors().Single();
22+
private static readonly ConstructorInfo _objectConstructor = typeof(object).GetConstructors().Single();
2323

24-
private static readonly Type[] ParameterTypes = [typeof(IClientProxy)];
24+
private static readonly Type[] _parameterTypes = [typeof(IClientProxy)];
2525

2626
public static T Build(IClientProxy proxy)
2727
{
@@ -89,13 +89,13 @@ private static IEnumerable<MethodInfo> GetAllInterfaceMethods(Type interfaceType
8989

9090
private static ConstructorInfo BuildConstructor(TypeBuilder type, FieldInfo proxyField)
9191
{
92-
var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ParameterTypes);
92+
var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, _parameterTypes);
9393

9494
var generator = ctor.GetILGenerator();
9595

9696
// Call object constructor
9797
generator.Emit(OpCodes.Ldarg_0);
98-
generator.Emit(OpCodes.Call, ObjectConstructor);
98+
generator.Emit(OpCodes.Call, _objectConstructor);
9999

100100
// Assign constructor argument to the proxyField
101101
generator.Emit(OpCodes.Ldarg_0); // type
@@ -217,7 +217,7 @@ private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo
217217
else
218218
{
219219
// Get 'CancellationToken.None' and put it on the stack, for when method does not have CancellationToken
220-
generator.Emit(OpCodes.Call, CancellationTokenNoneProperty.GetMethod!);
220+
generator.Emit(OpCodes.Call, _cancellationTokenNoneProperty.GetMethod!);
221221
}
222222

223223
// Send!
@@ -229,7 +229,7 @@ private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo
229229
private static void BuildFactoryMethod(TypeBuilder type, ConstructorInfo ctor)
230230
{
231231
var method = type.DefineMethod(nameof(Build), MethodAttributes.Public | MethodAttributes.Static,
232-
CallingConventions.Standard, typeof(T), ParameterTypes);
232+
CallingConventions.Standard, typeof(T), _parameterTypes);
233233

234234
var generator = method.GetILGenerator();
235235

ManagedCode.Orleans.SignalR.Core/Interfaces/IObserverConnectionManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
using System.Threading.Tasks;
12
using Orleans;
23
using Orleans.Concurrency;
3-
using System.Threading.Tasks;
44

55
namespace ManagedCode.Orleans.SignalR.Core.Interfaces;
66

0 commit comments

Comments
 (0)