From 19d55aa4267d88e3d7ab83a18f88b62c479319bf Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Wed, 14 Dec 2022 16:59:15 -0500 Subject: [PATCH] Test ExtensibleRate memory management --- test/python/test_reaction.py | 24 ++++++++++++++++++++++++ test/python/user_ext.py | 15 +++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/test/python/test_reaction.py b/test/python/test_reaction.py index b56486a23..75068a6af 100644 --- a/test/python/test_reaction.py +++ b/test/python/test_reaction.py @@ -2,6 +2,7 @@ from math import exp from pathlib import Path import sys import textwrap +import gc import cantera as ct import numpy as np @@ -1619,6 +1620,29 @@ class TestExtensible2(utilities.CanteraTest): with pytest.raises(ct.CanteraError, match="SyntaxError"): ct.Solution(yaml=self._input_template.format(module="user_ext_invalid")) + def test_memory_management(self): + # Make sure objects are being correctly cleaned up and not stuck in + # mixed Python/C++ ownership cycles + import user_ext + + gc.collect() + initialRate = user_ext.SquareRate.use_count[0] + initialData = user_ext.SquareRateData.use_count[0] + + def run(): + gas = ct.Solution("extensible-reactions.yaml", transport_model=None) + assert gas.forward_rate_constants[0] > 0 + assert user_ext.SquareRate.use_count[0] == initialRate + 1 + assert user_ext.SquareRateData.use_count[0] == initialData + 1 + + run() + + # The number of instances for both classes should go back to its previous value + # after deleting the Solution (may not be zero due to other Solution instances) + # held by other test classes + gc.collect() + assert user_ext.SquareRate.use_count[0] == initialRate + assert user_ext.SquareRateData.use_count[0] == initialData class InterfaceReactionTests(ReactionTests): # test suite for surface reaction expressions diff --git a/test/python/user_ext.py b/test/python/user_ext.py index eccceb769..4e7228460 100644 --- a/test/python/user_ext.py +++ b/test/python/user_ext.py @@ -2,17 +2,32 @@ import cantera as ct class SquareRateData(ct.ExtensibleRateData): __slots__ = ("Tsquared",) + use_count = [0] # used in test for memory leak + + def __init__(self): + self.use_count[0] += 1 def update(self, gas): self.Tsquared = gas.T**2 return True + def __del__(self): + self.use_count[0] -= 1 + @ct.extension(name="square-rate", data=SquareRateData) class SquareRate(ct.ExtensibleRate): __slots__ = ("A",) + use_count = [0] # used in test for memory leak + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.use_count[0] += 1 def set_parameters(self, node, units): self.A = node["A"] def eval(self, data): return self.A * data.Tsquared + + def __del__(self): + self.use_count[0] -= 1