Skip to content

Commit 9ee38d7

Browse files
committed
Wrap boost::python::object
Solves a race condition on the destruction of instances of boost::python::object
1 parent 681c7d7 commit 9ee38d7

File tree

1 file changed

+44
-10
lines changed

1 file changed

+44
-10
lines changed

SEImplementation/src/lib/Configuration/ModelFittingConfig.cpp

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ namespace SExtractor {
3636
* Elements::Exception if either the call or the extract throw a Python exception
3737
*/
3838
template <typename R, typename ...T>
39-
R py_call_wrapper(py::object func, T... args) {
39+
R py_call_wrapper(const py::object& func, T... args) {
4040
try {
4141
return py::extract<R>(func(args...));
4242
}
@@ -45,13 +45,47 @@ R py_call_wrapper(py::object func, T... args) {
4545
}
4646
}
4747

48+
/**
49+
* @brief Hold a reference to a Python object
50+
* @details
51+
* A boost::python::object contains a pointer to the underlying Python struct, which is
52+
* copied as-is (shared) when copied. When the boost::python::object is destroyed, it checks,
53+
* and then decrements, the reference count. This destruction is *not* thread safe, as the pointer
54+
* is not protected by a mutex or anything.
55+
* This class holds a single reference to the Python object, and relies on the mechanism of
56+
* std::shared_ptr to destroy the object once there is no one using it. std::shared_ptr *is*
57+
* thread safe, unlike boost::python::object.
58+
*/
59+
class PyObjectHolder {
60+
public:
61+
PyObjectHolder(py::object&& obj): m_obj_ptr(std::make_shared<py::object>(obj)) {}
62+
63+
PyObjectHolder(const PyObjectHolder&) = default;
64+
PyObjectHolder(PyObjectHolder&&) = default;
65+
66+
operator const py::object&() const {
67+
return *m_obj_ptr;
68+
}
69+
70+
const py::object& operator *() const {
71+
return *m_obj_ptr;
72+
}
73+
74+
py::object attr(const char *name) {
75+
return m_obj_ptr->attr(name);
76+
}
77+
78+
private:
79+
std::shared_ptr<py::object> m_obj_ptr;
80+
};
81+
4882
ModelFittingConfig::ModelFittingConfig(long manager_id) : Configuration(manager_id) {
4983
declareDependency<PythonConfig>();
5084
}
5185

5286
void ModelFittingConfig::initialize(const UserValues&) {
5387
for (auto& p : getDependency<PythonConfig>().getInterpreter().getConstantParameters()) {
54-
py::object py_value_func = p.second.attr("get_value")();
88+
auto py_value_func = PyObjectHolder(p.second.attr("get_value")());
5589
auto value_func = [py_value_func] (const SourceInterface& o) -> double {
5690
ObjectInfo oi {o};
5791
return py_call_wrapper<double>(py_value_func, oi);
@@ -61,25 +95,25 @@ void ModelFittingConfig::initialize(const UserValues&) {
6195
}
6296

6397
for (auto& p : getDependency<PythonConfig>().getInterpreter().getFreeParameters()) {
64-
py::object py_init_value_func = p.second.attr("get_init_value")();
98+
auto py_init_value_func = PyObjectHolder(p.second.attr("get_init_value")());
6599
auto init_value_func = [py_init_value_func] (const SourceInterface& o) -> double {
66100
ObjectInfo oi {o};
67101
return py_call_wrapper<double>(py_init_value_func, oi);
68102
};
69103

70-
py::object py_range_obj = p.second.attr("get_range")();
104+
auto py_range_obj = PyObjectHolder(p.second.attr("get_range")());
71105

72106
std::shared_ptr<FlexibleModelFittingConverterFactory> converter;
73107
std::string type_string(py::extract<char const*>(py_range_obj.attr("__class__").attr("__name__")));
74108
if (type_string == "Unbounded") {
75-
py::object py_factor_func = py_range_obj.attr("get_normalization_factor")();
109+
auto py_factor_func = PyObjectHolder(py_range_obj.attr("get_normalization_factor")());
76110
auto factor_func = [py_factor_func] (double init, const SourceInterface& o) -> double {
77111
ObjectInfo oi {o};
78112
return py_call_wrapper<double>(py_factor_func, init, oi);
79113
};
80114
converter = std::make_shared<FlexibleModelFittingUnboundedConverterFactory>(factor_func);
81115
} else if (type_string == "Range") {
82-
py::object py_range_func = py_range_obj.attr("get_limits")();
116+
auto py_range_func = PyObjectHolder(py_range_obj.attr("get_limits")());
83117
auto range_func = [py_range_func] (double init, const SourceInterface& o) -> std::pair<double, double> {
84118
ObjectInfo oi {o};
85119
py::tuple range = py_call_wrapper<py::tuple>(py_range_func, init, oi);
@@ -102,7 +136,7 @@ void ModelFittingConfig::initialize(const UserValues&) {
102136
}
103137

104138
for (auto& p : getDependency<PythonConfig>().getInterpreter().getDependentParameters()) {
105-
py::object py_func = p.second.attr("func");
139+
auto py_func = PyObjectHolder(p.second.attr("func"));
106140
std::vector<std::shared_ptr<FlexibleModelFittingParameter>> params {};
107141
py::list param_ids = py::extract<py::list>(p.second.attr("params"));
108142
for (int i = 0; i < py::len(param_ids); ++i) {
@@ -113,7 +147,7 @@ void ModelFittingConfig::initialize(const UserValues&) {
113147
auto dependent_func = [py_func](const std::shared_ptr<CoordinateSystem> &cs, const std::vector<double> &params) -> double {
114148
try {
115149
PythonInterpreter::getSingleton().setCoordinateSystem(cs);
116-
return py::extract<double>(py_func(*py::tuple(params)));
150+
return py::extract<double>((*py_func)(*py::tuple(params)));
117151
}
118152
catch (const py::error_already_set&) {
119153
throw pyToElementsException(logger);
@@ -182,12 +216,12 @@ void ModelFittingConfig::initialize(const UserValues&) {
182216
auto& prior = p.second;
183217
int param_id = py::extract<int>(prior.attr("param"));
184218
auto param = m_parameters[param_id];
185-
py::object py_value_func = prior.attr("value");
219+
auto py_value_func = PyObjectHolder(prior.attr("value"));
186220
auto value_func = [py_value_func] (const SourceInterface& o) -> double {
187221
ObjectInfo oi {o};
188222
return py_call_wrapper<double>(py_value_func, oi);
189223
};
190-
py::object py_sigma_func = prior.attr("sigma");
224+
auto py_sigma_func = PyObjectHolder(prior.attr("sigma"));
191225
auto sigma_func = [py_sigma_func] (const SourceInterface& o) -> double {
192226
ObjectInfo oi {o};
193227
return py_call_wrapper<double>(py_sigma_func, oi);

0 commit comments

Comments
 (0)