6
6
import threading
7
7
import time
8
8
import weakref
9
- from collections import deque
9
+ from collections import OrderedDict , deque
10
10
from enum import Enum
11
11
from functools import cached_property
12
12
from pathlib import Path , PurePath
@@ -103,8 +103,8 @@ def __repr__(self) -> str:
103
103
STATE_CHANGE_DELAY = 0.01 # Delay to avoid busy loops during state changes
104
104
EVALUATE_TIMEOUT = 120 # Timeout for keyword evaluation in seconds
105
105
KEYWORD_EVALUATION_TIMEOUT = 60 # Timeout for keyword evaluation wait in seconds
106
- MAX_EVALUATE_CACHE_SIZE = 50 # Maximum number of items in evaluate cache
107
106
MAX_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
108
108
109
109
110
110
# Type definitions for better type safety
@@ -370,7 +370,6 @@ def _get_instance(cls) -> "Debugger":
370
370
if cls .__instance is not None :
371
371
return cls .__instance
372
372
with cls .__lock :
373
- # re-check, perhaps it was created in the mean time...
374
373
if cls .__instance is None :
375
374
cls .__inside_instance = True
376
375
try :
@@ -433,7 +432,6 @@ def __init__(self) -> None:
433
432
434
433
self .debug_logger : Optional [DebugLogger ] = None
435
434
self .run_started = False
436
- self ._evaluate_cache : List [Any ] = []
437
435
self ._variables_cache : Dict [int , Any ] = {}
438
436
self ._variables_object_cache : List [Any ] = []
439
437
self ._current_exception : Optional [ExceptionInformation ] = None
@@ -450,9 +448,7 @@ def state(self, value: State) -> None:
450
448
State .Paused ,
451
449
State .CallKeyword ,
452
450
]:
453
- self ._variables_cache .clear ()
454
- self ._variables_object_cache .clear ()
455
- self ._evaluate_cache .clear ()
451
+ self ._clear_all_caches ()
456
452
457
453
time .sleep (STATE_CHANGE_DELAY )
458
454
@@ -493,6 +489,12 @@ def robot_output_file(self, value: Optional[str]) -> None:
493
489
def terminate (self ) -> None :
494
490
self .terminated = True
495
491
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
+
496
498
def start (self ) -> None :
497
499
with self .condition :
498
500
self .state = State .Running
@@ -1184,20 +1186,46 @@ def is_not_caughted_by_keyword(self) -> bool:
1184
1186
return r is None
1185
1187
1186
1188
__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
1187
1191
1188
1192
def _get_matcher (self , pattern_type : str ) -> Optional [Callable [[str , str ], bool ]]:
1189
- from robot .utils import Matcher
1190
-
1191
1193
if self .__matchers is None :
1192
1194
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 ,
1194
1196
"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 ,
1196
1198
"START" : lambda m , p : m .startswith (p ),
1197
1199
}
1198
1200
1199
1201
return self .__matchers .get (pattern_type .upper (), None )
1200
1202
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
+
1201
1229
def _should_run_except (self , branch : Any , error : str ) -> bool :
1202
1230
if not branch .patterns :
1203
1231
return True
@@ -1905,10 +1933,6 @@ def run_kw() -> Any:
1905
1933
return self ._create_evaluate_result (result )
1906
1934
1907
1935
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
-
1912
1936
if isinstance (value , Mapping ):
1913
1937
v_id = self ._new_cache_id ()
1914
1938
self ._variables_cache [v_id ] = value
@@ -1962,10 +1986,6 @@ def run_in_robot_thread(self, kw: KeywordCallable) -> EvaluationResult:
1962
1986
return result
1963
1987
1964
1988
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
-
1969
1989
if isinstance (value , Mapping ):
1970
1990
v_id = self ._new_cache_id ()
1971
1991
self ._variables_cache [v_id ] = value
0 commit comments