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

1"""Overload detection for identifying server-side overload 

2conditions.""" 

3 

4from http import HTTPStatus 

5from typing import Set 

6 

7from qreward.globals import ( 

8 LIBRARY_OVERLOAD_EXCEPTIONS_MAPPING, 

9 OVERLOAD_EXCEPTIONS, 

10 OVERLOAD_KEYWORDS, 

11 SYSTEM_OVERLOAD_INDICATORS, 

12) 

13 

14 

15MAX_EXCEPTION_CHAIN_DEPTH = 50 

16 

17 

18class OverloadChecker: 

19 """Checker for detecting server-side overload conditions from 

20 exceptions.""" 

21 

22 @staticmethod 

23 def check(exception: BaseException) -> bool: 

24 """Check if exception indicates server overload. 

25 

26 Iteratively walks the exception chain (via __cause__ and 

27 __context__) 

28 up to MAX_EXCEPTION_CHAIN_DEPTH levels to detect overload signals. 

29 

30 Args: 

31 exception: Exception to check 

32 

33 Returns: 

34 True if server is overloaded 

35 """ 

36 pending = [exception] 

37 visited: Set[int] = set() 

38 depth = 0 

39 

40 while pending and depth < MAX_EXCEPTION_CHAIN_DEPTH: 

41 current = pending.pop(0) 

42 current_id = id(current) 

43 

44 if current_id in visited: 

45 continue 

46 visited.add(current_id) 

47 depth += 1 

48 

49 if OverloadChecker._check_single(current): 

50 return True 

51 

52 for attr_name in ("__cause__", "__context__"): 

53 chained = getattr(current, attr_name, None) 

54 if chained is not None: 

55 pending.append(chained) 

56 

57 return False 

58 

59 @staticmethod 

60 def _check_single(exception: BaseException) -> bool: 

61 """Check a single exception (without following chains) for 

62 overload signals. 

63 

64 Args: 

65 exception: Exception to check 

66 

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 

80 

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 

91 

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 

100 

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 

106 

107 # 5. System-level check 

108 for indicator in SYSTEM_OVERLOAD_INDICATORS: 

109 if indicator in error_message: 

110 return True 

111 

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 

122 

123 return False