timemachine
timemachine copied to clipboard
Resolve ownership semantics of wrapped objects
The ownership and lifecycle semantics of wrapped objects is currently confusing and its easy to shoot yourself in the foot.
An example of valid vs invalid code using a context:
def get_valid_context(bps, integrator, x0, box):
# return integrator impl to avoid deallocating
intg = integrator.impl()
context = custom_ops.Context(x0, np.zeros_like(x0), box, intg, bps)
return intg, context
def get_invalid_context(bps, integrator, x0, box):
# Integrator implementation here will be cleaned up after the function goes out of scope
# Will segfault
intg = integrator.impl()
context = custom_ops.Context(x0, np.zeros_like(x0), box, intg, bps)
return context
We should resolve this in the C++ code to avoid having to modify how we write our python code.
Another possibility if this is difficult to resolve in the C++ bindings is to create Python wrappers to hold references to the C++ objects they depend on, e.g. for Context
something like:
class Context(custom_ops.Context):
def __init__(self, x0, v0, box, integ, bps):
# maintain references to C++ objects to prevent deallocation
self._integ = integ
self._bps = bps
super().__init__(x0, v0, box, integ, bps)
To clarify, I meant this issue to be about all of our C++ wrapped code and not specific to the context. We have this issue in a few places and it seems worthwhile to develop a convention and fix these ownership issues more broadly.
Makes sense. Given that this issue was used to resolve https://github.com/proteneer/timemachine/pull/789#discussion_r941822300, just wanted to make sure that the potential workaround mentioned there was not lost (and I think this technique should be generalizable beyond just the Context
objects).