diff --git a/src/fairvisor/circuit_breaker.lua b/src/fairvisor/circuit_breaker.lua index 1f2a33b..7535429 100644 --- a/src/fairvisor/circuit_breaker.lua +++ b/src/fairvisor/circuit_breaker.lua @@ -16,6 +16,7 @@ local tostring = tostring -- Fixed 1-minute rolling window; TTL for rate keys. Configurable window could be added later. local WINDOW_SIZE_SECONDS = 60 local WINDOW_TTL_SECONDS = 120 +local SECONDS_PER_MINUTE = 60 local STATE_PREFIX = "cb_state:" local RATE_PREFIX = "cb_rate:" local OPEN_PREFIX = "open:" @@ -116,7 +117,7 @@ function _M.check(dict, config, limit_key, cost, now) local opened_at = _parse_opened_at(state_raw) if opened_at then if config.auto_reset_after_minutes > 0 then - local elapsed_minutes = (now - opened_at) / WINDOW_SIZE_SECONDS + local elapsed_minutes = (now - opened_at) / SECONDS_PER_MINUTE if elapsed_minutes < config.auto_reset_after_minutes then return { tripped = true, @@ -129,6 +130,12 @@ function _M.check(dict, config, limit_key, cost, now) end dict:delete(state_key) + -- Clear rate keys so the sliding-window estimator does not immediately + -- re-trip the breaker on the first post-reset request. Mirrors _M.reset(). + local current_window_ar = floor(now / WINDOW_SIZE_SECONDS) * WINDOW_SIZE_SECONDS + local previous_window_ar = current_window_ar - WINDOW_SIZE_SECONDS + dict:delete(_M.build_rate_key(limit_key, current_window_ar)) + dict:delete(_M.build_rate_key(limit_key, previous_window_ar)) else return { tripped = true,