Skip to content

Commit

Permalink
Track references in boutpp
Browse files Browse the repository at this point in the history
The list from gc was incomplete.
Using a custom weakref list ensures we can free all objects before
MPI_Finalize is called.

This reverts 0e4314c, which was not working as it relied on the
same, incomplete, list of objects.

Resolves: gh#3041
  • Loading branch information
dschwoerer committed Jan 7, 2025
1 parent 56e3761 commit b864f32
Showing 1 changed file with 26 additions and 40 deletions.
66 changes: 26 additions & 40 deletions tools/pylib/_boutpp_build/boutpp.pyx.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,16 @@ from libcpp.memory cimport unique_ptr
from cython.operator import dereference as deref
import numbers
import sys
import gc
from time import sleep
import weakref

_allobjects = []

size_t = np.uint

cdef extern from "boutexception_helper.hxx":
cdef void raise_bout_py_error()

{% macro wait() %}
def _boutpp_wait_freed(self):
while not self._is_freed:
sleep(0.001)
{% endmacro %}

{% macro dealloc(extra_dealloc='', uniquePtr=False) %}
def __dealloc__(self):
self._boutpp_dealloc()
Expand All @@ -43,7 +39,6 @@ cdef extern from "boutexception_helper.hxx":
self.cobj = NULL
{% endif %}

{{ wait() }}
{% endmacro %}

{% macro class(name, init="", extra_dealloc="", defaultSO=True, data="", uniquePtr=False, init_args="*args, **kwargs",
Expand All @@ -58,10 +53,11 @@ cdef class {{ name }}{{ "(" ~ base ~ ")" if base else "" }}:
"""
cdef {{ "unique_ptr[c." ~ name ~ "]" if uniquePtr else "c." ~ name ~ "* " }} cobj
cdef c.bool isSelfOwned
cdef c.bool _is_freed
cdef object __weakref__
{{ data | indent }}
def __cinit__(self, {{ init_args}}):
self._is_freed = False
global _allobjects
_allobjects.append(weakref.ref(self))
{% if not uniquePtr and cobj %}
self.isSelfOwned = {{ defaultSO }}
self.cobj = NULL
Expand Down Expand Up @@ -939,14 +935,15 @@ cdef class PythonModelCallback:
Needed for callbacks from C++ to python
"""
cdef c.PythonModelCallback * cobj
cdef c.bool _is_freed
cdef c.bool isSelfOwned
cdef object __weakref__

def __cinit__(self, method):
# "callback" :: The pattern/converter method to fire a Python
# object method from C typed infos
# "method" :: The effective method passed by the Python user
self._is_freed = False
global _allobjects
_allobjects.append(weakref.ref(self))
self.isSelfOwned = True
checkInit()
self.cobj = new c.PythonModelCallback(callback, <void*>method)
Expand All @@ -965,16 +962,17 @@ cdef class PhysicsModelBase:
cdef c.PythonModelCallback * callback
cdef c.PythonModelCallback * callbackinit
cdef c.bool _done_pyinit
cdef c.bool _is_freed
cdef c.bool isSelfOwned
cdef object __weakref__

def __cinit__(self):
checkInit()
global _allobjects
_allobjects.append(weakref.ref(self))
self.cmodel = new c.PythonModel()
self.callback = NULL
self.callbackinit = NULL
self._done_pyinit = False
self._is_freed = False
self.isSelfOwned = True

def solve(self):
Expand Down Expand Up @@ -1037,9 +1035,6 @@ cdef class PhysicsModelBase:
del self.callback
del self.callbackinit
self.cmodel = <c.PythonModel *> 0
self._is_freed = True

{{ wait() }}


class PhysicsModel(PhysicsModelBase):
Expand Down Expand Up @@ -1181,34 +1176,25 @@ def finalise():
global _isInit
wasInit = _isInit
_isInit = False
objects = gc.get_objects()
objects = _allobjects #gc.get_objects()
ourClasses = (
Field2D, Field3D, Vector2D, Vector3D, Mesh, Coordinates,
Laplacian, FieldFactory, PythonModelCallback, PhysicsModel,
PhysicsModelBase, PythonModelCallback, Options,
)
for obj in objects:
if isinstance(obj, ourClasses):
if hasattr(obj, "_boutpp_dealloc"):
obj._boutpp_dealloc()
else:
for ourClass in ourClasses:
if isinstance(obj, ourClass):
if hasattr(ourClass, "_boutpp_dealloc"):
ourClass._boutpp_dealloc(obj)
break
for obj in objects:
if isinstance(obj, ourClasses):
if hasattr(obj, "_boutpp_wait_freed"):
obj._boutpp_wait_freed()
else:
for ourClass in ourClasses:
if isinstance(obj, ourClass):
if hasattr(ourClass, "_boutpp_wait_freed"):
ourClass._boutpp_wait_freed(obj)
break
else:
print(f"{obj} of type {ourClass} cannot wait!")
for objr in objects:
obj = objr()
if obj is None:
continue
if hasattr(obj, "_boutpp_dealloc"):
obj._boutpp_dealloc()
else:
for ourClass in ourClasses:
if isinstance(obj, ourClass):
if hasattr(ourClass, "_boutpp_dealloc"):
ourClass._boutpp_dealloc(obj)
break

del objects
# Actually finalise
if wasInit:
Expand Down

0 comments on commit b864f32

Please sign in to comment.