Skip to content

Commit bda45c6

Browse files
author
Karl Solgård
committed
Fix concerns after review
1 parent 0a4b139 commit bda45c6

12 files changed

Lines changed: 81 additions & 21 deletions

File tree

src/ConcurrencyLimits.AspNetCore/ConcurrencyLimitMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public async Task InvokeAsync(HttpContext context)
4343
throw;
4444
}
4545
}
46-
else
46+
else if (!context.Response.HasStarted)
4747
{
4848
context.Response.StatusCode = _throttleStatus;
4949
await context.Response.WriteAsync("Concurrency limit exceeded");

src/ConcurrencyLimits.Grpc/Client/ConcurrencyLimitClientInterceptor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public override TResponse BlockingUnaryCall<TRequest, TResponse>(
3434
listener.OnSuccess();
3535
return response;
3636
}
37-
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
37+
catch (RpcException ex) when (ex.StatusCode is StatusCode.Unavailable or StatusCode.DeadlineExceeded or StatusCode.Cancelled)
3838
{
3939
listener.OnDropped();
4040
throw;
@@ -85,7 +85,7 @@ private static async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> i
8585
listener.OnSuccess();
8686
return response;
8787
}
88-
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
88+
catch (RpcException ex) when (ex.StatusCode is StatusCode.Unavailable or StatusCode.DeadlineExceeded or StatusCode.Cancelled)
8989
{
9090
listener.OnDropped();
9191
throw;

src/ConcurrencyLimits/Internal/SystemNanoTime.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,20 @@ namespace ConcurrencyLimits.Internal;
77
/// </summary>
88
public static class SystemNanoTime
99
{
10-
private static readonly double NanosPerTick = 1_000_000_000.0 / Stopwatch.Frequency;
10+
private const long NanosPerSecond = 1_000_000_000L;
11+
private static readonly long Frequency = Stopwatch.Frequency;
1112

1213
/// <summary>
1314
/// Current value of a monotonic clock, in nanoseconds. Only differences between
1415
/// two readings are meaningful.
1516
/// </summary>
16-
public static long Now() => (long)(Stopwatch.GetTimestamp() * NanosPerTick);
17+
public static long Now()
18+
{
19+
// Integer math avoids the double-mantissa precision loss that a (ticks * nanosPerTick)
20+
// multiply suffers once the timestamp grows large.
21+
long ticks = Stopwatch.GetTimestamp();
22+
long whole = ticks / Frequency * NanosPerSecond;
23+
long frac = ticks % Frequency * NanosPerSecond / Frequency;
24+
return whole + frac;
25+
}
1726
}

src/ConcurrencyLimits/Limit/AbstractLimit.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ public abstract class AbstractLimit : ILimit
66
private readonly List<Action<int>> _listeners = new();
77
private readonly object _sync = new();
88

9+
/// <summary>Lock guarding algorithm state. Subclasses must hold this when reading mutable
10+
/// state outside <see cref="Update"/> (e.g. in accessors or ToString) to avoid torn reads.</summary>
11+
protected object SyncRoot => _sync;
12+
913
protected AbstractLimit(int initialLimit) => _limit = initialLimit;
1014

1115
public void OnSample(long startTime, long rtt, int inflight, bool didDrop)

src/ConcurrencyLimits/Limit/Gradient2Limit.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,27 @@ protected override int Update(long startTime, long rtt, int inflight, bool didDr
164164
return (int)newLimit;
165165
}
166166

167-
public long GetLastRttNanos() => _lastRtt;
167+
public long GetLastRttNanos()
168+
{
169+
lock (SyncRoot)
170+
{
171+
return _lastRtt;
172+
}
173+
}
168174

169-
public long GetRttNoLoadNanos() => (long)_longRtt.Get();
175+
public long GetRttNoLoadNanos()
176+
{
177+
lock (SyncRoot)
178+
{
179+
return (long)_longRtt.Get();
180+
}
181+
}
170182

171-
public override string ToString() => $"Gradient2Limit [limit={(int)_estimatedLimit}]";
183+
public override string ToString()
184+
{
185+
lock (SyncRoot)
186+
{
187+
return $"Gradient2Limit [limit={(int)_estimatedLimit}]";
188+
}
189+
}
172190
}

src/ConcurrencyLimits/Limit/GradientLimit.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,27 @@ protected override int Update(long startTime, long rtt, int inflight, bool didDr
208208
return (int)_estimatedLimit;
209209
}
210210

211-
public long GetLastRttNanos() => _lastRtt;
211+
public long GetLastRttNanos()
212+
{
213+
lock (SyncRoot)
214+
{
215+
return _lastRtt;
216+
}
217+
}
212218

213-
public long GetRttNoLoadNanos() => (long)_rttNoLoadMeasurement.Get();
219+
public long GetRttNoLoadNanos()
220+
{
221+
lock (SyncRoot)
222+
{
223+
return (long)_rttNoLoadMeasurement.Get();
224+
}
225+
}
214226

215-
public override string ToString() => $"GradientLimit [limit={(int)_estimatedLimit}, rtt_noload={GetRttNoLoadNanos() / 1e6} ms]";
227+
public override string ToString()
228+
{
229+
lock (SyncRoot)
230+
{
231+
return $"GradientLimit [limit={(int)_estimatedLimit}, rtt_noload={(long)_rttNoLoadMeasurement.Get() / 1e6} ms]";
232+
}
233+
}
216234
}

src/ConcurrencyLimits/Limit/VegasLimit.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,11 @@ private int UpdateEstimatedLimit(long rtt, long rttNoLoad, int inflight, bool di
240240
return (int)newLimit;
241241
}
242242

243-
public override string ToString() => $"VegasLimit [limit={GetLimit()}, rtt_noload={_rttNoLoad / 1e6} ms]";
243+
public override string ToString()
244+
{
245+
lock (SyncRoot)
246+
{
247+
return $"VegasLimit [limit={GetLimit()}, rtt_noload={_rttNoLoad / 1e6} ms]";
248+
}
249+
}
244250
}

src/ConcurrencyLimits/Limit/WindowedLimit.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public void OnSample(long startTime, long rtt, int inflight, bool didDrop)
9999
{
100100
try
101101
{
102-
if (endTime > _nextUpdateTime)
102+
if (endTime > Volatile.Read(ref _nextUpdateTime))
103103
{
104104
ISampleWindow current = Interlocked.Exchange(ref _sample, _sampleWindowFactory.NewInstance());
105105
Volatile.Write(ref _nextUpdateTime,

src/ConcurrencyLimits/Limiter/AbstractLimiter.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ protected AbstractLimiter(AbstractLimiterBuilder builder)
3636
_limitAlgorithm.NotifyOnChange(OnNewLimit);
3737
_bypassResolver = builder.BypassResolver;
3838

39+
string name = builder.ResolveName();
3940
builder.Registry.Gauge(MetricIds.LimitName, () => GetLimit());
40-
_successCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, builder.Name, StatusTag, "success");
41-
_droppedCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, builder.Name, StatusTag, "dropped");
42-
_ignoredCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, builder.Name, StatusTag, "ignored");
43-
_rejectedCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, builder.Name, StatusTag, "rejected");
44-
_bypassCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, builder.Name, StatusTag, "bypassed");
41+
_successCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, name, StatusTag, "success");
42+
_droppedCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, name, StatusTag, "dropped");
43+
_ignoredCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, name, StatusTag, "ignored");
44+
_rejectedCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, name, StatusTag, "rejected");
45+
_bypassCounter = builder.Registry.Counter(MetricIds.CallName, IdTag, name, StatusTag, "bypassed");
4546
}
4647

4748
public abstract IListener? Acquire(TContext context);

src/ConcurrencyLimits/Limiter/AbstractLimiterBuilder.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ public abstract class AbstractLimiterBuilder
1111
{
1212
internal ILimit Limit = VegasLimit.NewDefault();
1313
internal Func<long> Clock = SystemNanoTime.Now;
14-
internal string Name = "unnamed-" + Interlocked.Increment(ref _idCounter);
14+
internal string? Name;
1515
internal IMetricRegistry Registry = EmptyMetricRegistry.Instance;
1616

1717
internal static readonly Func<object?, bool> AlwaysFalse = _ => false;
1818
internal Func<object?, bool> BypassResolver = AlwaysFalse;
1919

2020
private static int _idCounter;
21+
22+
/// <summary>Resolve the limiter name, allocating an "unnamed-N" id lazily at build time so
23+
/// ids reflect actual limiters built, not every builder instantiated.</summary>
24+
internal string ResolveName() => Name ??= "unnamed-" + Interlocked.Increment(ref _idCounter);
2125
}
2226

2327
public abstract class AbstractLimiterBuilder<TBuilder> : AbstractLimiterBuilder

0 commit comments

Comments
 (0)