Skip to content

Commit 709800c

Browse files
authored
Merge pull request #60 from ebertin/fix/python_obj_race
Fix race condition on the destruction of boost::python::object instances
2 parents ae9bcb6 + 9ee38d7 commit 709800c

File tree

5 files changed

+71
-25
lines changed

5 files changed

+71
-25
lines changed

SEImplementation/SEImplementation/Plugin/FlexibleModelFitting/FlexibleModelFittingParameter.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
#include <functional>
1414
#include <vector>
1515
#include <mutex>
16-
17-
#include <boost/python/object.hpp>
16+
#include "SEFramework/CoordinateSystem/CoordinateSystem.h"
1817

1918
namespace ModelFitting {
2019
class BasicParameter;
@@ -93,8 +92,12 @@ class FlexibleModelFittingFreeParameter : public FlexibleModelFittingParameter {
9392
class FlexibleModelFittingDependentParameter : public FlexibleModelFittingParameter {
9493

9594
public:
95+
96+
/// The signature of a function that evaluates the dependent parameter. It gets
97+
/// as parameters the values of those parameters on which this one depends.
98+
using ValueFunc = std::function<double(const std::shared_ptr<CoordinateSystem>&, const std::vector<double>&)>;
9699

97-
FlexibleModelFittingDependentParameter(int id, boost::python::object value_calculator,
100+
FlexibleModelFittingDependentParameter(int id, ValueFunc value_calculator,
98101
std::vector<std::shared_ptr<FlexibleModelFittingParameter>> parameters)
99102
: FlexibleModelFittingParameter(id),
100103
m_value_calculator(value_calculator),
@@ -106,8 +109,8 @@ class FlexibleModelFittingDependentParameter : public FlexibleModelFittingParame
106109
const SourceInterface& source) const override;
107110

108111
private:
109-
110-
boost::python::object m_value_calculator;
112+
113+
ValueFunc m_value_calculator;
111114
std::vector<std::shared_ptr<FlexibleModelFittingParameter>> m_parameters;
112115

113116
};

SEImplementation/src/lib/Configuration/ModelFittingConfig.cpp

Lines changed: 55 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,15 +136,26 @@ 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) {
109143
int id = py::extract<int>(param_ids[i]);
110144
params.push_back(m_parameters[id]);
111145
}
146+
147+
auto dependent_func = [py_func](const std::shared_ptr<CoordinateSystem> &cs, const std::vector<double> &params) -> double {
148+
try {
149+
PythonInterpreter::getSingleton().setCoordinateSystem(cs);
150+
return py::extract<double>((*py_func)(*py::tuple(params)));
151+
}
152+
catch (const py::error_already_set&) {
153+
throw pyToElementsException(logger);
154+
}
155+
};
156+
112157
m_parameters[p.first] = std::make_shared<FlexibleModelFittingDependentParameter>(
113-
p.first, py_func, params);
158+
p.first, dependent_func, params);
114159
}
115160

116161
for (auto& p : getDependency<PythonConfig>().getInterpreter().getPointSourceModels()) {
@@ -171,12 +216,12 @@ void ModelFittingConfig::initialize(const UserValues&) {
171216
auto& prior = p.second;
172217
int param_id = py::extract<int>(prior.attr("param"));
173218
auto param = m_parameters[param_id];
174-
py::object py_value_func = prior.attr("value");
219+
auto py_value_func = PyObjectHolder(prior.attr("value"));
175220
auto value_func = [py_value_func] (const SourceInterface& o) -> double {
176221
ObjectInfo oi {o};
177222
return py_call_wrapper<double>(py_value_func, oi);
178223
};
179-
py::object py_sigma_func = prior.attr("sigma");
224+
auto py_sigma_func = PyObjectHolder(prior.attr("sigma"));
180225
auto sigma_func = [py_sigma_func] (const SourceInterface& o) -> double {
181226
ObjectInfo oi {o};
182227
return py_call_wrapper<double>(py_sigma_func, oi);

SEImplementation/src/lib/Plugin/FlexibleModelFitting/FlexibleModelFittingParameter.cpp

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
#include <iostream>
9-
#include <boost/python.hpp>
109

1110
#include "ModelFitting/utils.h"
1211

@@ -77,19 +76,14 @@ template<typename ... Parameters>
7776
std::shared_ptr<ModelFitting::BasicParameter> createDependentParameterHelper(
7877
FlexibleModelFittingParameterManager& parameter_manager,
7978
const SourceInterface& source,
80-
boost::python::object value_calculator,
79+
FlexibleModelFittingDependentParameter::ValueFunc value_calculator,
8180
std::shared_ptr<Parameters>... parameters) {
8281
auto coordinate_system = source.getProperty<DetectionFrame>().getFrame()->getCoordinateSystem();
8382

8483
auto calc = [value_calculator, coordinate_system] (decltype(doubleResolver(std::declval<Parameters>()))... params) -> double {
8584
std::lock_guard<std::mutex> guard {python_callback_mutex};
86-
try {
87-
PythonInterpreter::getSingleton().setCoordinateSystem(coordinate_system);
88-
return boost::python::extract<double>(value_calculator(params...));
89-
}
90-
catch (const boost::python::error_already_set &e) {
91-
throw pyToElementsException(logger);
92-
}
85+
std::vector<double> materialized{params...};
86+
return value_calculator(coordinate_system, materialized);
9387
};
9488
return createDependentParameterPtr(calc, *(parameter_manager.getParameter(source, parameters))...);
9589
}

SEImplementation/src/lib/Plugin/FlexibleModelFitting/FlexibleModelFittingPrior.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*/
77

88
#include <iostream>
9-
#include <boost/python.hpp>
109
#include <mutex>
1110

1211
#include "ModelFitting/utils.h"

SEImplementation/src/lib/PythonConfig/PythonModule.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
#include <boost/python.hpp>
7+
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
78

89
#include <SEImplementation/PythonConfig/ObjectInfo.h>
910
#include <SEImplementation/PythonConfig/PyMeasurementImage.h>
@@ -61,6 +62,10 @@ BOOST_PYTHON_MODULE(_SExtractorPy) {
6162
.def(bp::init<double, double>())
6263
.def_readwrite("x", &ImageCoordinate::m_x)
6364
.def_readwrite("y", &ImageCoordinate::m_y);
65+
66+
bp::class_<std::vector<double> >("_DoubleVector")
67+
.def(bp::vector_indexing_suite<std::vector<double> >())
68+
;
6469
}
6570

6671
} // namespace SExtractor

0 commit comments

Comments
 (0)