66import threading
77import time
88import weakref
9- from collections import deque
9+ from collections import OrderedDict , deque
1010from enum import Enum
1111from functools import cached_property
1212from pathlib import Path , PurePath
@@ -103,8 +103,8 @@ def __repr__(self) -> str:
103103STATE_CHANGE_DELAY = 0.01 # Delay to avoid busy loops during state changes
104104EVALUATE_TIMEOUT = 120 # Timeout for keyword evaluation in seconds
105105KEYWORD_EVALUATION_TIMEOUT = 60 # Timeout for keyword evaluation wait in seconds
106- MAX_EVALUATE_CACHE_SIZE = 50 # Maximum number of items in evaluate cache
107106MAX_VARIABLE_ITEMS_DISPLAY = 500 # Maximum items to display in variable view
107+ MAX_REGEX_CACHE_SIZE = 25 # Maximum number of compiled regex patterns to cache
108108
109109
110110# Type definitions for better type safety
@@ -370,7 +370,6 @@ def _get_instance(cls) -> "Debugger":
370370 if cls .__instance is not None :
371371 return cls .__instance
372372 with cls .__lock :
373- # re-check, perhaps it was created in the mean time...
374373 if cls .__instance is None :
375374 cls .__inside_instance = True
376375 try :
@@ -433,7 +432,6 @@ def __init__(self) -> None:
433432
434433 self .debug_logger : Optional [DebugLogger ] = None
435434 self .run_started = False
436- self ._evaluate_cache : List [Any ] = []
437435 self ._variables_cache : Dict [int , Any ] = {}
438436 self ._variables_object_cache : List [Any ] = []
439437 self ._current_exception : Optional [ExceptionInformation ] = None
@@ -450,9 +448,7 @@ def state(self, value: State) -> None:
450448 State .Paused ,
451449 State .CallKeyword ,
452450 ]:
453- self ._variables_cache .clear ()
454- self ._variables_object_cache .clear ()
455- self ._evaluate_cache .clear ()
451+ self ._clear_all_caches ()
456452
457453 time .sleep (STATE_CHANGE_DELAY )
458454
@@ -493,6 +489,12 @@ def robot_output_file(self, value: Optional[str]) -> None:
493489 def terminate (self ) -> None :
494490 self .terminated = True
495491
492+ def _clear_all_caches (self ) -> None :
493+ """Optimized method to clear all caches in one operation."""
494+ self ._variables_cache .clear ()
495+ self ._variables_object_cache .clear ()
496+ self .__compiled_regex_cache .clear ()
497+
496498 def start (self ) -> None :
497499 with self .condition :
498500 self .state = State .Running
@@ -1184,20 +1186,46 @@ def is_not_caughted_by_keyword(self) -> bool:
11841186 return r is None
11851187
11861188 __matchers : Optional [Dict [str , Callable [[str , str ], bool ]]] = None
1189+ __compiled_regex_cache : "OrderedDict[str, re.Pattern[str]]" = OrderedDict ()
1190+ __robot_matcher : Optional [Any ] = None
11871191
11881192 def _get_matcher (self , pattern_type : str ) -> Optional [Callable [[str , str ], bool ]]:
1189- from robot .utils import Matcher
1190-
11911193 if self .__matchers is None :
11921194 self .__matchers : Dict [str , Callable [[str , str ], bool ]] = {
1193- "GLOB" : lambda m , p : bool ( Matcher ( p , spaceless = False , caseless = False ). match ( m )) ,
1195+ "GLOB" : self . _glob_matcher ,
11941196 "LITERAL" : lambda m , p : m == p ,
1195- "REGEXP" : lambda m , p : re . match ( rf" { p } \Z" , m ) is not None ,
1197+ "REGEXP" : self . _regexp_matcher ,
11961198 "START" : lambda m , p : m .startswith (p ),
11971199 }
11981200
11991201 return self .__matchers .get (pattern_type .upper (), None )
12001202
1203+ def _glob_matcher (self , message : str , pattern : str ) -> bool :
1204+ """Optimized glob matcher with cached Robot Matcher."""
1205+ if self .__robot_matcher is None :
1206+ from robot .utils import Matcher
1207+
1208+ self .__robot_matcher = Matcher
1209+
1210+ return bool (self .__robot_matcher (pattern , spaceless = False , caseless = False ).match (message ))
1211+
1212+ def _regexp_matcher (self , message : str , pattern : str ) -> bool :
1213+ """Optimized regex matcher with LRU caching (max 25 entries)."""
1214+ if pattern in self .__compiled_regex_cache :
1215+ self .__compiled_regex_cache .move_to_end (pattern )
1216+ compiled_pattern = self .__compiled_regex_cache [pattern ]
1217+ else :
1218+ if len (self .__compiled_regex_cache ) >= MAX_REGEX_CACHE_SIZE :
1219+ self .__compiled_regex_cache .popitem (last = False )
1220+
1221+ try :
1222+ compiled_pattern = re .compile (rf"{ pattern } \Z" )
1223+ self .__compiled_regex_cache [pattern ] = compiled_pattern
1224+ except re .error :
1225+ return False
1226+
1227+ return compiled_pattern .match (message ) is not None
1228+
12011229 def _should_run_except (self , branch : Any , error : str ) -> bool :
12021230 if not branch .patterns :
12031231 return True
@@ -1905,10 +1933,6 @@ def run_kw() -> Any:
19051933 return self ._create_evaluate_result (result )
19061934
19071935 def _create_evaluate_result (self , value : Any ) -> EvaluateResult :
1908- self ._evaluate_cache .insert (0 , value )
1909- if len (self ._evaluate_cache ) > MAX_EVALUATE_CACHE_SIZE :
1910- self ._evaluate_cache .pop ()
1911-
19121936 if isinstance (value , Mapping ):
19131937 v_id = self ._new_cache_id ()
19141938 self ._variables_cache [v_id ] = value
@@ -1962,10 +1986,6 @@ def run_in_robot_thread(self, kw: KeywordCallable) -> EvaluationResult:
19621986 return result
19631987
19641988 def _create_set_variable_result (self , value : Any ) -> SetVariableResult :
1965- self ._evaluate_cache .insert (0 , value )
1966- if len (self ._evaluate_cache ) > MAX_EVALUATE_CACHE_SIZE :
1967- self ._evaluate_cache .pop ()
1968-
19691989 if isinstance (value , Mapping ):
19701990 v_id = self ._new_cache_id ()
19711991 self ._variables_cache [v_id ] = value
0 commit comments