@@ -0,0 +1,71 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Hashable
|
||||
|
||||
from devplacepy.services.nodes.spec import SPEC
|
||||
|
||||
LIMITS = SPEC.limits
|
||||
|
||||
CLOSED = "closed"
|
||||
OPEN = "open"
|
||||
HALF_OPEN = "half_open"
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Circuit:
|
||||
failures: int = 0
|
||||
opened_at: float = 0.0
|
||||
probing: bool = False
|
||||
|
||||
|
||||
class CircuitBreaker:
|
||||
def __init__(
|
||||
self,
|
||||
failures: int = LIMITS.breaker_failures,
|
||||
open_seconds: float = LIMITS.breaker_open_seconds,
|
||||
clock: Callable[[], float] = time.monotonic,
|
||||
) -> None:
|
||||
self.failures = max(1, failures)
|
||||
self.open_seconds = open_seconds
|
||||
self._clock = clock
|
||||
self._circuits: dict[Hashable, _Circuit] = {}
|
||||
|
||||
def state(self, key: Hashable) -> str:
|
||||
circuit = self._circuits.get(key)
|
||||
if circuit is None or circuit.failures < self.failures:
|
||||
return CLOSED
|
||||
if self._clock() - circuit.opened_at < self.open_seconds:
|
||||
return OPEN
|
||||
return HALF_OPEN
|
||||
|
||||
def allows(self, key: Hashable) -> bool:
|
||||
state = self.state(key)
|
||||
if state == CLOSED:
|
||||
return True
|
||||
if state == OPEN:
|
||||
return False
|
||||
return not self._circuits[key].probing
|
||||
|
||||
def begin(self, key: Hashable) -> None:
|
||||
if self.state(key) == HALF_OPEN:
|
||||
self._circuits[key].probing = True
|
||||
|
||||
def success(self, key: Hashable) -> None:
|
||||
self._circuits.pop(key, None)
|
||||
|
||||
def failure(self, key: Hashable) -> None:
|
||||
circuit = self._circuits.setdefault(key, _Circuit())
|
||||
circuit.probing = False
|
||||
circuit.failures += 1
|
||||
if circuit.failures >= self.failures:
|
||||
circuit.opened_at = self._clock()
|
||||
|
||||
def forget(self, key: Hashable) -> None:
|
||||
self._circuits.pop(key, None)
|
||||
|
||||
def open_count(self) -> int:
|
||||
return sum(1 for key in list(self._circuits) if self.state(key) != CLOSED)
|
||||
Reference in New Issue
Block a user