// Copyright 2021-2025 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Adapted from server/rate_counter.go in the NATS server Go source. namespace ZB.MOM.NatsNet.Server.Internal; /// /// A sliding-window rate limiter that allows at most limit events /// per window. /// Mirrors rateCounter in server/rate_counter.go. /// public sealed class RateCounter { private readonly long _limit; private long _count; private ulong _blocked; private DateTime _end; // Exposed for tests (mirrors direct field access in rate_counter_test.go). public TimeSpan Interval; private readonly object _lock = new(); public RateCounter(long limit) { _limit = limit; Interval = TimeSpan.FromSeconds(1); } /// /// Factory wrapper for Go parity. /// Mirrors newRateCounter. /// public static RateCounter NewRateCounter(long limit) => new(limit); /// /// Returns true if the event is within the rate limit for the current window. /// Mirrors rateCounter.allow. /// public bool Allow() { var now = DateTime.UtcNow; lock (_lock) { if (now > _end) { _count = 0; _end = now + Interval; } else { _count++; } var allow = _count < _limit; if (!allow) _blocked++; return allow; } } /// /// Returns and resets the count of blocked events since the last call. /// Mirrors rateCounter.countBlocked. /// public ulong CountBlocked() { lock (_lock) { var blocked = _blocked; _blocked = 0; return blocked; } } }