Skip to content

Commit fce67c9

Browse files
authored
Use weak references for last context in pydantic validation (#582)
* Use weakref for previous context maintenance Signed-off-by: Nijat Khanbabayev <nijat.khanbabayev@cubistsystematic.com> * Add test for TVarValidationContext garbage collection Signed-off-by: Nijat Khanbabayev <nijat.khanbabayev@cubistsystematic.com> --------- Signed-off-by: Nijat Khanbabayev <nijat.khanbabayev@cubistsystematic.com>
1 parent 70843ab commit fce67c9

2 files changed

Lines changed: 30 additions & 3 deletions

File tree

csp/impl/types/typing_utils.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44
import types
55
import typing
6+
import weakref
67

78
import numpy
89

@@ -178,7 +179,9 @@ def __init__(self, source_type: type):
178179
f"Argument to ts must either be: a type, ForwardRef or TypeVar. Got {source_type} which is an instance of {type(source_type)}."
179180
)
180181
self._last_value_type = None
181-
self._last_context = None
182+
# Use a weak reference to the last validation context to avoid
183+
# keeping TVarValidationContext instances alive
184+
self._last_context_ref = None
182185

183186
def validate(self, value_type, info=None):
184187
"""Run the validation against a proposed input type"""
@@ -189,10 +192,14 @@ def validate(self, value_type, info=None):
189192
# is equal to the last value_type, and if so, skip validation (as any errors would already have been thrown)
190193
# We also don't test equality on info, assuming that the same validation info object is used
191194
# for a given validation run.
192-
if value_type == self._last_value_type and info is not None and self._last_context is info.context:
195+
last_context = None if self._last_context_ref is None else self._last_context_ref()
196+
if value_type == self._last_value_type and info is not None and last_context is info.context:
193197
return value_type
194198
self._last_value_type = value_type
195-
self._last_context = info.context if info is not None else None
199+
if info is not None and info.context is not None:
200+
self._last_context_ref = weakref.ref(info.context)
201+
else:
202+
self._last_context_ref = None
196203

197204
if value_type is typing.Any:
198205
# https://docs.python.org/3/library/typing.html#the-any-type

csp/tests/test_engine.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,26 @@ def stop(x: ts[bool]) -> ts[bool]:
425425
result = csp.run(stop, csp.timer(timedelta(seconds=1)), starttime=datetime(2020, 5, 19))[0]
426426
self.assertEqual(len(result), 5)
427427

428+
def test_tvar_validation_context_lifetime(self):
429+
import gc
430+
431+
from csp.impl.types.pydantic_type_resolver import TVarValidationContext
432+
433+
def count_contexts():
434+
return sum(1 for o in gc.get_objects() if type(o) is TVarValidationContext)
435+
436+
@csp.node
437+
def echo(x: ts[int]) -> ts[int]:
438+
return x
439+
440+
gc.collect(0)
441+
before = count_contexts()
442+
csp.build_graph(echo, realtime=False, x=csp.const(1))
443+
gc.collect(0)
444+
after = count_contexts()
445+
446+
self.assertEqual(before, after)
447+
428448
def test_class_member_node(self):
429449
class ClassWithNodes:
430450
def __init__(self):

0 commit comments

Comments
 (0)