RpcRateLimitInterceptor constructor

RpcRateLimitInterceptor({
  1. RpcRateLimit? global,
  2. Map<String, RpcRateLimit> perService = const {},
  3. Map<String, RpcRateLimit> perMethod = const {},
  4. RpcRateLimit? perKeyFallback,
  5. String? keyExtractor(
    1. RpcCallInfo
    )?,
  6. Duration cleanupInterval = const Duration(minutes: 5),
  7. int maxTrackedKeys = 100000,
  8. bool meterServerStreamMessages = false,
  9. DateTime clock()?,
})

Creates a rate limiter.

When keyExtractor is provided, perService and perMethod counters are created dynamically per extracted key. global is always shared.

cleanupInterval controls how often stale per-key counters are evicted (only relevant when keyExtractor is set).

clock replaces the time source, as on RpcContext.withClock, for a fake clock in tests. The default is a monotonic Stopwatch read as microseconds, so wall-clock jumps cannot break the windows and a call allocates no DateTime.

Implementation

RpcRateLimitInterceptor({
  RpcRateLimit? global,
  Map<String, RpcRateLimit> perService = const {},
  Map<String, RpcRateLimit> perMethod = const {},
  RpcRateLimit? perKeyFallback,
  String? Function(RpcCallInfo)? keyExtractor,
  Duration cleanupInterval = const Duration(minutes: 5),
  int maxTrackedKeys = 100000,
  bool meterServerStreamMessages = false,
  DateTime Function()? clock,
}) : _globalSpec = global,
     _perServiceSpec = Map.unmodifiable(perService),
     _perMethodSpec = Map.unmodifiable(perMethod),
     _perKeyFallbackSpec = perKeyFallback,
     _keyExtractor = keyExtractor,
     _meterServerStreamMessages = meterServerStreamMessages,
     _cleanupIntervalUs = cleanupInterval.inMicroseconds,
     _nowMicros = clock == null
         ? _defaultMonotonicMicros
         : (() => clock().microsecondsSinceEpoch) {
  // A throw, not an assert, which vanishes in release. At 0 the store evicts
  // each key as it is inserted, so every call gets a fresh counter and no
  // limit is ever enforced -- the opposite of what the operator asked for.
  if (maxTrackedKeys <= 0) {
    throw ArgumentError.value(
      maxTrackedKeys,
      'maxTrackedKeys',
      'must be > 0',
    );
  }

  _dynamicServiceCounters = _KeyedLruStore(maxTrackedKeys);
  _dynamicMethodCounters = _KeyedLruStore(maxTrackedKeys);
  _dynamicFallbackCounters = _KeyedLruStore(maxTrackedKeys);

  // Validate EVERY spec up front, in every build mode. A spec that cannot
  // enforce anything must fail loudly at construction rather than at first
  // use -- or, worse, never (see RpcRateLimit._validate).
  global?._validate();
  _perKeyFallbackSpec?._validate();
  for (final spec in perService.values) {
    spec._validate();
  }
  for (final spec in perMethod.values) {
    spec._validate();
  }

  _globalCounter = global?._createCounter(_nowMicros);
  _lastCleanupUs = _nowMicros();

  if (keyExtractor == null) {
    // Static mode: create one counter per slot up front.
    _staticServiceCounters = {
      for (final e in perService.entries)
        e.key: e.value._createCounter(_nowMicros),
    };
    _staticMethodCounters = {
      for (final e in perMethod.entries)
        e.key: e.value._createCounter(_nowMicros),
    };
  } else {
    _staticServiceCounters = const {};
    _staticMethodCounters = const {};
  }
}