< Summary

Information
Class: Intentum.Runtime.RateLimiting.MemoryRateLimiter
Assembly: Intentum.Runtime
File(s): /home/runner/work/Intentum/Intentum/src/Intentum.Runtime/RateLimiting/MemoryRateLimiter.cs
Line coverage
97%
Covered lines: 40
Uncovered lines: 1
Coverable lines: 41
Total lines: 73
Line coverage: 97.5%
Branch coverage
87%
Covered branches: 7
Total branches: 8
Branch coverage: 87.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%11100%
TryAcquireAsync(...)100%44100%
Reset(...)100%22100%
.ctor(...)100%11100%
Tick(...)50%2280%

File(s)

/home/runner/work/Intentum/Intentum/src/Intentum.Runtime/RateLimiting/MemoryRateLimiter.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2
 3namespace Intentum.Runtime.RateLimiting;
 4
 5/// <summary>
 6/// In-memory rate limiter using a fixed window per key.
 7/// Suitable for single-node or development; use a distributed implementation for multi-node.
 8/// </summary>
 9public sealed class MemoryRateLimiter : IRateLimiter
 10{
 1011    private readonly ConcurrentDictionary<string, Window> _windows = new();
 12
 13    /// <inheritdoc />
 14    public ValueTask<RateLimitResult> TryAcquireAsync(
 15        string key,
 16        int limit,
 17        TimeSpan window,
 18        CancellationToken cancellationToken = default)
 1919    {
 1920        cancellationToken.ThrowIfCancellationRequested();
 21
 1922        var now = DateTimeOffset.UtcNow;
 1923        var windowKey = $"{key}:{window.Ticks}";
 1924        var current = _windows.AddOrUpdate(
 1925            windowKey,
 926            _ => new Window(now, window, 1),
 2927            (_, w) => w.Tick(now));
 28
 1929        var allowed = current.Count <= limit;
 1930        TimeSpan? retryAfter = null;
 1931        if (!allowed && current.WindowEnd > now)
 332            retryAfter = current.WindowEnd - now;
 33
 1934        var result = new RateLimitResult(
 1935            Allowed: allowed,
 1936            CurrentCount: current.Count,
 1937            Limit: limit,
 1938            RetryAfter: retryAfter);
 39
 1940        return ValueTask.FromResult(result);
 1941    }
 42
 43    /// <inheritdoc />
 44    public void Reset(string key)
 145    {
 246        var toRemove = _windows.Keys.Where(k => k.StartsWith(key + ":", StringComparison.Ordinal)).ToList();
 547        foreach (var k in toRemove)
 148            _windows.TryRemove(k, out _);
 149    }
 50
 51    private sealed class Window
 52    {
 1053        public DateTimeOffset WindowStart { get; }
 1654        public DateTimeOffset WindowEnd { get; }
 1055        public TimeSpan Duration { get; }
 4856        public int Count { get; }
 57
 1958        public Window(DateTimeOffset start, TimeSpan duration, int count)
 1959        {
 1960            WindowStart = start;
 1961            Duration = duration;
 1962            WindowEnd = start + duration;
 1963            Count = count;
 1964        }
 65
 66        public Window Tick(DateTimeOffset now)
 1067        {
 1068            if (now >= WindowEnd)
 069                return new Window(now, Duration, 1);
 1070            return new Window(WindowStart, Duration, Count + 1);
 1071        }
 72    }
 73}