2#ifndef _KURLYK_HTTP_RATE_LIMITER_HPP_INCLUDED
3#define _KURLYK_HTTP_RATE_LIMITER_HPP_INCLUDED
31 std::lock_guard<std::mutex> lock(
m_mutex);
48 [
this](
long limit_id) {
65 return handle ? handle->id() : 0;
79 std::unique_lock<std::mutex> lock(
m_mutex);
86 retired_handle = std::move(it->second);
108 std::lock_guard<std::mutex> lock(
m_mutex);
131 uint64_t in_flight_token,
132 const std::string& general_key,
133 const std::string& specific_key
135 std::lock_guard<std::mutex> lock(
m_mutex);
137 const long general_id = general_limit ? general_limit->id() : 0;
138 const long specific_id = specific_limit ? specific_limit->id() : 0;
140 auto general_it = general_id != 0 ?
m_limits.find(general_id) :
m_limits.end();
141 auto specific_it = specific_id != 0 ?
m_limits.find(specific_id) :
m_limits.end();
147 const auto now = std::chrono::steady_clock::now();
149 bool general_limit_allowed =
true;
150 bool specific_limit_allowed =
true;
153 if (general_it->second.removed) {
154 general_limit_allowed =
true;
156 general_limit_allowed =
can_pass(general_it->second, general_key, in_flight_token, now);
160 if (specific_it !=
m_limits.end()) {
161 if (specific_it->second.removed) {
162 specific_limit_allowed =
true;
164 specific_limit_allowed =
can_pass(specific_it->second, specific_key, in_flight_token, now);
168 if (!general_limit_allowed || !specific_limit_allowed) {
172 const bool same_limit =
174 general_id == specific_id &&
175 general_key == specific_key;
177 if (general_it !=
m_limits.end() && !general_it->second.removed) {
178 commit_limit(general_it->second, general_key, in_flight_token, now);
181 if (!same_limit && specific_it !=
m_limits.end() && !specific_it->second.removed) {
182 commit_limit(specific_it->second, specific_key, in_flight_token, now);
198 return allow_request(general_limit, specific_limit, 0, std::string(), std::string());
205 bool allow_request(
long general_rate_limit_id,
long specific_rate_limit_id) {
226 uint64_t in_flight_token,
227 const std::string& general_key,
228 const std::string& specific_key) {
229 if (in_flight_token == 0)
return;
231 std::lock_guard<std::mutex> lock(
m_mutex);
233 const long general_id = general_limit ? general_limit->id() : 0;
234 const long specific_id = specific_limit ? specific_limit->id() : 0;
236 const bool same_limit =
238 general_id == specific_id &&
239 general_key == specific_key;
241 auto general_it = general_id != 0 ?
m_limits.find(general_id) :
m_limits.end();
242 auto specific_it = specific_id != 0 ?
m_limits.find(specific_id) :
m_limits.end();
244 if (general_it !=
m_limits.end() && general_it->second.sequential) {
245 release_key(general_it->second, general_key, in_flight_token);
248 if (!same_limit && specific_it !=
m_limits.end() && specific_it->second.sequential) {
249 release_key(specific_it->second, specific_key, in_flight_token);
257 uint64_t in_flight_token) {
258 release_request(general_limit, specific_limit, in_flight_token, std::string(), std::string());
270 template<
typename Duration = std::chrono::milliseconds>
274 const std::string& general_key,
275 const std::string& specific_key
277 std::lock_guard<std::mutex> lock(
m_mutex);
279 const auto now = std::chrono::steady_clock::now();
284 const long general_id = general_limit ? general_limit->id() : 0;
285 const long specific_id = specific_limit ? specific_limit->id() : 0;
291 if (general_delay == (Duration::max)()) {
300 if (specific_delay == (Duration::max)()) {
309 template<
typename Duration = std::chrono::milliseconds>
323 template<
typename Duration = std::chrono::milliseconds>
340 template<
typename Duration = std::chrono::milliseconds>
342 std::lock_guard<std::mutex> lock(
m_mutex);
344 const auto now = std::chrono::steady_clock::now();
350 Duration min_delay = (Duration::max)();
351 bool has_positive_delay =
false;
354 const auto& limit = pair.second;
355 for (
const auto& key_pair : limit.keys) {
357 if (delay.count() <= 0) {
360 has_positive_delay =
true;
361 if (delay < min_delay) {
367 if (has_positive_delay) {
393 std::unordered_map<std::string, KeyState>
keys;
405 std::lock_guard<std::mutex> lock(
m_mutex);
410 auto& limit = it->second;
411 limit.removed =
true;
413 if (!limit.keys.empty()) {
416 return m_limits.erase(limit_id) > 0;
421 return limit.
keys[key];
426 auto it = limit.
keys.find(key);
427 if (it == limit.
keys.end()) {
457 const auto elapsed_time =
458 std::chrono::duration_cast<std::chrono::milliseconds>(
462 if (elapsed_time.count() >= limit_data.
period_ms) {
487 const auto elapsed_time =
488 std::chrono::duration_cast<std::chrono::milliseconds>(
492 if (elapsed_time.count() >= limit_data.
period_ms) {
502 auto it = limit.
keys.find(key);
503 if (it == limit.
keys.end()) {
506 auto& state = it->second;
507 state.in_flight_tokens.erase(token);
508 if (state.in_flight_tokens.empty() && state.count == 0) {
509 limit.
keys.erase(it);
519 template<
typename Duration>
522 const std::string& key,
539 template<
typename Duration>
547 return (Duration::max)();
555 std::chrono::duration_cast<Duration>(now - state.
start_time);
557 const auto period_duration =
558 std::chrono::duration_cast<Duration>(
559 std::chrono::milliseconds(limit.
period_ms)
562 if (elapsed >= period_duration ||
567 return period_duration - elapsed;
573 auto& limit = limit_it->second;
574 for (
auto key_it = limit.keys.begin(); key_it != limit.keys.end(); ) {
575 const auto& state = key_it->second;
576 if (state.in_flight_tokens.empty() && state.count == 0) {
577 key_it = limit.keys.erase(key_it);
578 }
else if (state.in_flight_tokens.empty()) {
580 std::chrono::duration_cast<std::chrono::milliseconds>(
581 now - state.start_time
583 if (elapsed.count() >= limit.period_ms) {
584 key_it = limit.keys.erase(key_it);
593 if (limit.removed && limit.keys.empty()) {
594 limit_it =
m_limits.erase(limit_it);
RAII handle that owns a registered HTTP rate-limit ID.
Manages rate limits for HTTP requests.
std::unordered_map< long, LimitData > m_limits
Physically alive limit data.
void gc_stale_keys(const time_point_t &now)
Erases keys that are empty or whose period has expired.
bool remove_limit(long limit_id)
Releases manager-owned handle for the specified limit ID.
HttpRateLimitHandlePtr get_limit(long limit_id)
Returns manager-owned handle by ID.
bool allow_request(long general_rate_limit_id, long specific_rate_limit_id)
Legacy API: checks if request is allowed by two limit IDs.
std::chrono::steady_clock::time_point time_point_t
HttpRateLimitHandlePtr create_limit_handle(long requests_per_period, long period_ms, bool sequential=false)
Creates a new rate limit and returns its RAII handle.
bool check_key(const LimitData &limit_data, const KeyState &state, const time_point_t &now) const
bool remove_limit_internal(long limit_id)
Marks a limit as removed and erases it only when no key state remains.
void commit_limit(LimitData &limit, const std::string &key, uint64_t token, const time_point_t &now)
Commits in-flight token and count/period state after a successful can_pass.
bool allow_request(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit)
Handle-based overload without explicit token or keys (token = 0, keys empty).
bool can_pass(const LimitData &limit, const std::string &key, uint64_t token, const time_point_t &now) const
Checks if a key within a limit can pass both sequential and count/period constraints.
RateLimitDelay< Duration > time_until_next_allowed(long general_rate_limit_id, long specific_rate_limit_id)
Legacy API: calculates delay by limit IDs.
void update_key(LimitData &limit_data, KeyState &state, const time_point_t &now)
void release_request(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, uint64_t in_flight_token, const std::string &general_key, const std::string &specific_key)
Releases in-flight tokens for sequential rate limits.
RateLimitDelay< Duration > time_until_any_limit_allows()
Finds the shortest delay among all physically alive limits.
bool allow_request(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, uint64_t in_flight_token, const std::string &general_key, const std::string &specific_key)
Checks if a request is allowed by two optional rate-limit handles with in-flight token tracking for s...
KeyState & get_key_state(LimitData &limit, const std::string &key)
Looks up a KeyState by key, creating it lazily if necessary.
long create_limit(long requests_per_period, long period_ms)
Legacy API: creates a new rate limit and returns only its ID.
void release_key(LimitData &limit, const std::string &key, uint64_t token)
Releases an in-flight token from a specific key and erases the key if it has no runtime state.
Duration time_until_limit_allows(const LimitData &limit, const std::string &key, const time_point_t &now) const
Duration time_until_key_allows(const LimitData &limit, const KeyState &state, const time_point_t &now) const
bool remove_limit(const HttpRateLimitHandlePtr &handle)
Releases manager-owned handle for the specified limit handle.
std::unordered_map< long, HttpRateLimitHandlePtr > m_owned_handles
Manager-owned handles for limits created through create_limit_handle().
RateLimitDelay< Duration > time_until_next_allowed(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit)
Legacy overload without explicit partition keys (uses default shared state).
const KeyState * find_key_state(const LimitData &limit, const std::string &key) const
Looks up a KeyState by key for read-only access (no insertion).
RateLimitDelay< Duration > time_until_next_allowed(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, const std::string &general_key, const std::string &specific_key)
void release_request(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, uint64_t in_flight_token)
Legacy overload without keys.
Primary namespace for the Kurlyk library, encompassing initialization, request management,...
std::shared_ptr< HttpRateLimitHandle > HttpRateLimitHandlePtr
Shared RAII handle for HTTP rate limits.
Per-key mutable runtime state inside a rate limit.
std::unordered_set< uint64_t > in_flight_tokens
Immutable limit parameters plus per-key mutable state.
bool removed
`true` when the manager-owned handle has been released; physical erase is deferred until all keys are...
std::unordered_map< std::string, KeyState > keys
Mutable state per partition key.
bool sequential
When `true`, blocks other requests until the current one finishes.
Result type for time-until-allowed queries.
Duration duration
Delay until the limit allows a request. 0 means ready now.
bool sequential_blocked
true if duration reflects Duration::max() because a sequential in-flight request is blocking.