Skip to content

Commit 56f0a7b

Browse files
Fix memoryleak in filters.
1 parent 427f4bc commit 56f0a7b

File tree

1 file changed

+40
-60
lines changed

1 file changed

+40
-60
lines changed

Diff for: src/prompt_toolkit/filters/base.py

+40-60
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import ABCMeta, abstractmethod
2-
from typing import Callable, Dict, Iterable, List, Tuple, Union
2+
from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union
33

44
__all__ = ["Filter", "Never", "Always", "Condition", "FilterOrBool"]
55

@@ -12,6 +12,11 @@ class Filter(metaclass=ABCMeta):
1212
The return value of ``__call__`` will tell if the feature should be active.
1313
"""
1414

15+
def __init__(self) -> None:
16+
self._and_cache: Dict[Filter, _AndList] = {}
17+
self._or_cache: Dict[Filter, _AndList] = {}
18+
self._invert_result: Optional[Filter] = None
19+
1520
@abstractmethod
1621
def __call__(self) -> bool:
1722
"""
@@ -23,19 +28,40 @@ def __and__(self, other: "Filter") -> "Filter":
2328
"""
2429
Chaining of filters using the & operator.
2530
"""
26-
return _and_cache[self, other]
31+
assert isinstance(other, Filter), "Expecting filter, got %r" % other
32+
33+
if isinstance(other, Always):
34+
return self
35+
if isinstance(other, Never):
36+
return other
37+
38+
result = _AndList([self, other])
39+
self._and_cache[other] = result
40+
return result
2741

2842
def __or__(self, other: "Filter") -> "Filter":
2943
"""
3044
Chaining of filters using the | operator.
3145
"""
32-
return _or_cache[self, other]
46+
assert isinstance(other, Filter), "Expecting filter, got %r" % other
47+
48+
if isinstance(other, Always):
49+
return other
50+
if isinstance(other, Never):
51+
return self
52+
53+
result = _OrList([self, other])
54+
self._or_cache[other] = result
55+
return result
3356

3457
def __invert__(self) -> "Filter":
3558
"""
3659
Inverting of filters using the ~ operator.
3760
"""
38-
return _invert_cache[self]
61+
if self._invert_result is None:
62+
self._invert_result = _Invert(self)
63+
64+
return self._invert_result
3965

4066
def __bool__(self) -> None:
4167
"""
@@ -52,68 +78,13 @@ def __bool__(self) -> None:
5278
)
5379

5480

55-
class _AndCache(Dict[Tuple[Filter, Filter], "_AndList"]):
56-
"""
57-
Cache for And operation between filters.
58-
(Filter classes are stateless, so we can reuse them.)
59-
60-
Note: This could be a memory leak if we keep creating filters at runtime.
61-
If that is True, the filters should be weakreffed (not the tuple of
62-
filters), and tuples should be removed when one of these filters is
63-
removed. In practise however, there is a finite amount of filters.
64-
"""
65-
66-
def __missing__(self, filters: Tuple[Filter, Filter]) -> Filter:
67-
a, b = filters
68-
assert isinstance(b, Filter), "Expecting filter, got %r" % b
69-
70-
if isinstance(b, Always) or isinstance(a, Never):
71-
return a
72-
elif isinstance(b, Never) or isinstance(a, Always):
73-
return b
74-
75-
result = _AndList(filters)
76-
self[filters] = result
77-
return result
78-
79-
80-
class _OrCache(Dict[Tuple[Filter, Filter], "_OrList"]):
81-
"""Cache for Or operation between filters."""
82-
83-
def __missing__(self, filters: Tuple[Filter, Filter]) -> Filter:
84-
a, b = filters
85-
assert isinstance(b, Filter), "Expecting filter, got %r" % b
86-
87-
if isinstance(b, Always) or isinstance(a, Never):
88-
return b
89-
elif isinstance(b, Never) or isinstance(a, Always):
90-
return a
91-
92-
result = _OrList(filters)
93-
self[filters] = result
94-
return result
95-
96-
97-
class _InvertCache(Dict[Filter, "_Invert"]):
98-
"""Cache for inversion operator."""
99-
100-
def __missing__(self, filter: Filter) -> Filter:
101-
result = _Invert(filter)
102-
self[filter] = result
103-
return result
104-
105-
106-
_and_cache = _AndCache()
107-
_or_cache = _OrCache()
108-
_invert_cache = _InvertCache()
109-
110-
11181
class _AndList(Filter):
11282
"""
11383
Result of &-operation between several filters.
11484
"""
11585

11686
def __init__(self, filters: Iterable[Filter]) -> None:
87+
super().__init__()
11788
self.filters: List[Filter] = []
11889

11990
for f in filters:
@@ -135,6 +106,7 @@ class _OrList(Filter):
135106
"""
136107

137108
def __init__(self, filters: Iterable[Filter]) -> None:
109+
super().__init__()
138110
self.filters: List[Filter] = []
139111

140112
for f in filters:
@@ -156,6 +128,7 @@ class _Invert(Filter):
156128
"""
157129

158130
def __init__(self, filter: Filter) -> None:
131+
super().__init__()
159132
self.filter = filter
160133

161134
def __call__(self) -> bool:
@@ -173,6 +146,9 @@ class Always(Filter):
173146
def __call__(self) -> bool:
174147
return True
175148

149+
def __or__(self, other: "Filter") -> "Filter":
150+
return self
151+
176152
def __invert__(self) -> "Never":
177153
return Never()
178154

@@ -185,6 +161,9 @@ class Never(Filter):
185161
def __call__(self) -> bool:
186162
return False
187163

164+
def __and__(self, other: "Filter") -> "Filter":
165+
return self
166+
188167
def __invert__(self) -> Always:
189168
return Always()
190169

@@ -204,6 +183,7 @@ def feature_is_active(): # `feature_is_active` becomes a Filter.
204183
"""
205184

206185
def __init__(self, func: Callable[[], bool]) -> None:
186+
super().__init__()
207187
self.func = func
208188

209189
def __call__(self) -> bool:

0 commit comments

Comments
 (0)