states.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # -*- coding: utf-8 -*-
  2. """
  3. Circuit-Breaker-Zustände und Zustandsübergänge.
  4. Definiert die Zustandsmaschine für den Circuit-Breaker.
  5. """
  6. from __future__ import annotations
  7. from dataclasses import dataclass, field
  8. from datetime import datetime
  9. from enum import Enum, auto
  10. class CircuitState(Enum):
  11. """
  12. Zustände des Circuit-Breakers.
  13. """
  14. CLOSED = auto()
  15. """
  16. Geschlossen - Normalbetrieb.
  17. Requests werden durchgelassen, Fehler werden gezählt.
  18. """
  19. OPEN = auto()
  20. """
  21. Offen - Alle Requests werden abgelehnt.
  22. Wartet auf Timeout bevor HALF_OPEN erreicht wird.
  23. """
  24. HALF_OPEN = auto()
  25. """
  26. Halb-Offen - Testphase.
  27. Begrenzte Anzahl Requests werden durchgelassen.
  28. Bei Erfolg → CLOSED, bei Fehler → OPEN.
  29. """
  30. class CircuitEvent(Enum):
  31. """
  32. Ereignisse die Zustandsübergänge auslösen.
  33. """
  34. SUCCESS = auto()
  35. """Erfolgreicher Request."""
  36. FAILURE = auto()
  37. """Fehlgeschlagener Request."""
  38. TIMEOUT = auto()
  39. """Open-Timeout abgelaufen."""
  40. FORCE_OPEN = auto()
  41. """Manuelles Öffnen."""
  42. FORCE_CLOSE = auto()
  43. """Manuelles Schließen."""
  44. FORCE_RESET = auto()
  45. """Manueller Reset."""
  46. @dataclass
  47. class StateTransition:
  48. """
  49. Ein Zustandsübergang im Circuit-Breaker.
  50. """
  51. from_state: CircuitState
  52. """Ausgangszustand."""
  53. to_state: CircuitState
  54. """Zielzustand."""
  55. event: CircuitEvent
  56. """Auslösendes Ereignis."""
  57. timestamp: datetime = field(default_factory=datetime.now)
  58. """Zeitpunkt des Übergangs."""
  59. reason: str = ""
  60. """Grund für den Übergang."""
  61. def __str__(self) -> str:
  62. """String-Repräsentation."""
  63. return (
  64. f"{self.from_state.name} -> {self.to_state.name} "
  65. f"({self.event.name})"
  66. )
  67. class CircuitStateMachine:
  68. """
  69. Zustandsmaschine für den Circuit-Breaker.
  70. Verwaltet die Zustandsübergänge basierend auf Ereignissen.
  71. """
  72. # Erlaubte Übergänge: (von, event) -> zu
  73. TRANSITIONS: dict[tuple[CircuitState, CircuitEvent], CircuitState] = {
  74. # CLOSED Zustand
  75. (CircuitState.CLOSED, CircuitEvent.SUCCESS): CircuitState.CLOSED,
  76. (CircuitState.CLOSED, CircuitEvent.FAILURE): CircuitState.CLOSED, # Oder OPEN bei Threshold
  77. (CircuitState.CLOSED, CircuitEvent.FORCE_OPEN): CircuitState.OPEN,
  78. # OPEN Zustand
  79. (CircuitState.OPEN, CircuitEvent.TIMEOUT): CircuitState.HALF_OPEN,
  80. (CircuitState.OPEN, CircuitEvent.FORCE_CLOSE): CircuitState.CLOSED,
  81. (CircuitState.OPEN, CircuitEvent.FORCE_RESET): CircuitState.CLOSED,
  82. # HALF_OPEN Zustand
  83. (CircuitState.HALF_OPEN, CircuitEvent.SUCCESS): CircuitState.CLOSED, # Oder HALF_OPEN
  84. (CircuitState.HALF_OPEN, CircuitEvent.FAILURE): CircuitState.OPEN,
  85. (CircuitState.HALF_OPEN, CircuitEvent.FORCE_OPEN): CircuitState.OPEN,
  86. (CircuitState.HALF_OPEN, CircuitEvent.FORCE_CLOSE): CircuitState.CLOSED,
  87. }
  88. def __init__(self, initial_state: CircuitState = CircuitState.CLOSED) -> None:
  89. """
  90. Initialisiert die Zustandsmaschine.
  91. Args:
  92. initial_state: Anfangszustand.
  93. """
  94. self._state = initial_state
  95. self._history: list[StateTransition] = []
  96. @property
  97. def state(self) -> CircuitState:
  98. """Gibt den aktuellen Zustand zurück."""
  99. return self._state
  100. @property
  101. def history(self) -> list[StateTransition]:
  102. """Gibt die Übergangshistorie zurück."""
  103. return list(self._history)
  104. def can_transition(
  105. self,
  106. event: CircuitEvent,
  107. ) -> tuple[bool, CircuitState | None]:
  108. """
  109. Prüft, ob ein Übergang möglich ist.
  110. Args:
  111. event: Das Ereignis.
  112. Returns:
  113. Tupel (möglich, Zielzustand).
  114. """
  115. key = (self._state, event)
  116. if key in self.TRANSITIONS:
  117. return True, self.TRANSITIONS[key]
  118. return False, None
  119. def transition(
  120. self,
  121. event: CircuitEvent,
  122. reason: str = "",
  123. ) -> StateTransition | None:
  124. """
  125. Führt einen Zustandsübergang durch.
  126. Args:
  127. event: Das auslösende Ereignis.
  128. reason: Optionaler Grund.
  129. Returns:
  130. StateTransition oder None wenn ungültig.
  131. """
  132. can_do, new_state = self.can_transition(event)
  133. if not can_do or new_state is None:
  134. return None
  135. old_state = self._state
  136. self._state = new_state
  137. transition = StateTransition(
  138. from_state=old_state,
  139. to_state=new_state,
  140. event=event,
  141. reason=reason,
  142. )
  143. self._history.append(transition)
  144. # Historie begrenzen
  145. if len(self._history) > 100:
  146. self._history = self._history[-50:]
  147. return transition
  148. def force_state(
  149. self,
  150. state: CircuitState,
  151. reason: str = "Forced",
  152. ) -> StateTransition:
  153. """
  154. Erzwingt einen bestimmten Zustand.
  155. Args:
  156. state: Zielzustand.
  157. reason: Grund.
  158. Returns:
  159. StateTransition.
  160. """
  161. old_state = self._state
  162. self._state = state
  163. transition = StateTransition(
  164. from_state=old_state,
  165. to_state=state,
  166. event=CircuitEvent.FORCE_RESET,
  167. reason=reason,
  168. )
  169. self._history.append(transition)
  170. return transition
  171. def reset(self) -> None:
  172. """Setzt die Zustandsmaschine zurück."""
  173. self._state = CircuitState.CLOSED
  174. self._history.clear()