43 lines
1.3 KiB
Python
43 lines
1.3 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import logging
|
||
|
|
import time
|
||
|
|
from collections import defaultdict
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
class JiraLoopBreaker:
|
||
|
|
def __init__(self, max_comments_per_issue_per_minute: int = 8):
|
||
|
|
self._counters: dict[str, list[float]] = defaultdict(list)
|
||
|
|
self._max = max_comments_per_issue_per_minute
|
||
|
|
self._tripped: dict[str, float] = {}
|
||
|
|
|
||
|
|
def record_and_check(self, issue_key: str) -> bool:
|
||
|
|
now = time.monotonic()
|
||
|
|
window = [t for t in self._counters.get(issue_key, []) if now - t < 60]
|
||
|
|
if len(window) >= self._max:
|
||
|
|
self._tripped[issue_key] = now
|
||
|
|
logger.warning(
|
||
|
|
"Jira loop breaker tripped for issue=%s, comments=%d",
|
||
|
|
issue_key,
|
||
|
|
len(window),
|
||
|
|
)
|
||
|
|
return False
|
||
|
|
window.append(now)
|
||
|
|
self._counters[issue_key] = window
|
||
|
|
return True
|
||
|
|
|
||
|
|
def is_tripped(self, issue_key: str) -> bool:
|
||
|
|
now = time.monotonic()
|
||
|
|
trip_time = self._tripped.get(issue_key)
|
||
|
|
if trip_time and now - trip_time < 300:
|
||
|
|
return True
|
||
|
|
if trip_time and now - trip_time >= 300:
|
||
|
|
del self._tripped[issue_key]
|
||
|
|
return False
|
||
|
|
|
||
|
|
def reset_issue(self, issue_key: str):
|
||
|
|
self._counters.pop(issue_key, None)
|
||
|
|
self._tripped.pop(issue_key, None)
|