Coverage for qreward / utils / scheduler / overload.py: 100.00%
48 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-23 19:32 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-23 19:32 +0000
1"""Overload detection for identifying server-side overload
2conditions."""
4from http import HTTPStatus
5from typing import Set
7from qreward.globals import (
8 LIBRARY_OVERLOAD_EXCEPTIONS_MAPPING,
9 OVERLOAD_EXCEPTIONS,
10 OVERLOAD_KEYWORDS,
11 SYSTEM_OVERLOAD_INDICATORS,
12)
15MAX_EXCEPTION_CHAIN_DEPTH = 50
18class OverloadChecker:
19 """Checker for detecting server-side overload conditions from
20 exceptions."""
22 @staticmethod
23 def check(exception: BaseException) -> bool:
24 """Check if exception indicates server overload.
26 Iteratively walks the exception chain (via __cause__ and
27 __context__)
28 up to MAX_EXCEPTION_CHAIN_DEPTH levels to detect overload signals.
30 Args:
31 exception: Exception to check
33 Returns:
34 True if server is overloaded
35 """
36 pending = [exception]
37 visited: Set[int] = set()
38 depth = 0
40 while pending and depth < MAX_EXCEPTION_CHAIN_DEPTH:
41 current = pending.pop(0)
42 current_id = id(current)
44 if current_id in visited:
45 continue
46 visited.add(current_id)
47 depth += 1
49 if OverloadChecker._check_single(current):
50 return True
52 for attr_name in ("__cause__", "__context__"):
53 chained = getattr(current, attr_name, None)
54 if chained is not None:
55 pending.append(chained)
57 return False
59 @staticmethod
60 def _check_single(exception: BaseException) -> bool:
61 """Check a single exception (without following chains) for
62 overload signals.
64 Args:
65 exception: Exception to check
67 Returns:
68 True if this specific exception indicates server overload
69 """
70 # 1. HTTP status code check
71 if hasattr(exception, "status_code"):
72 status_code = exception.status_code
73 if status_code in (
74 HTTPStatus.SERVICE_UNAVAILABLE.value,
75 HTTPStatus.TOO_MANY_REQUESTS.value,
76 HTTPStatus.BAD_GATEWAY.value,
77 HTTPStatus.GATEWAY_TIMEOUT.value,
78 ):
79 return True
81 # 2. Exception type check (full module path)
82 exception_type_full = (
83 f"{type(exception).__module__}.{type(exception).__name__}"
84 )
85 exception_type_name = type(exception).__name__
86 if (
87 exception_type_full in OVERLOAD_EXCEPTIONS
88 or exception_type_name in OVERLOAD_EXCEPTIONS
89 ):
90 return True
92 # 3. Library-specific exception handling
93 for lib_name, exceptions in (
94 LIBRARY_OVERLOAD_EXCEPTIONS_MAPPING.items()
95 ):
96 if lib_name in exception_type_full and any(
97 exc in exception_type_name for exc in exceptions
98 ):
99 return True
101 # 4. Exception message content check
102 error_message = str(exception).lower()
103 for keyword in OVERLOAD_KEYWORDS:
104 if keyword in error_message:
105 return True
107 # 5. System-level check
108 for indicator in SYSTEM_OVERLOAD_INDICATORS:
109 if indicator in error_message:
110 return True
112 # 6. Check exception args
113 if hasattr(exception, "args") and exception.args:
114 if any(
115 isinstance(arg, str)
116 and any(
117 keyword in arg.lower() for keyword in OVERLOAD_KEYWORDS
118 )
119 for arg in exception.args
120 ):
121 return True
123 return False