Skip to content

Commit 2edfeea

Browse files
committed
refactor(debugger): optimize cache management and improve regex matching performance
1 parent b78937a commit 2edfeea

File tree

1 file changed

+39
-19
lines changed

1 file changed

+39
-19
lines changed

packages/debugger/src/robotcode/debugger/debugger.py

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import threading
77
import time
88
import weakref
9-
from collections import deque
9+
from collections import OrderedDict, deque
1010
from enum import Enum
1111
from functools import cached_property
1212
from pathlib import Path, PurePath
@@ -103,8 +103,8 @@ def __repr__(self) -> str:
103103
STATE_CHANGE_DELAY = 0.01 # Delay to avoid busy loops during state changes
104104
EVALUATE_TIMEOUT = 120 # Timeout for keyword evaluation in seconds
105105
KEYWORD_EVALUATION_TIMEOUT = 60 # Timeout for keyword evaluation wait in seconds
106-
MAX_EVALUATE_CACHE_SIZE = 50 # Maximum number of items in evaluate cache
107106
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
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

Comments
 (0)