Refactored how/when python interpretor is created to simplify library usage

This commit is contained in:
matlabbe
2026-05-26 11:01:04 -07:00
parent 49aa566613
commit 7681e83c32
12 changed files with 48 additions and 52 deletions
+12 -4
View File
@@ -21,16 +21,24 @@ class gil_scoped_release;
namespace rtabmap {
/**
* Create a single PythonInterface on main thread at
* global scope before any Python classes.
* Process-wide singleton owning the embedded Python interpreter.
* Call PythonInterface::instance() from the main thread (typically near
* the top of main()) before any Python-using class is constructed.
*/
class RTABMAP_CORE_EXPORT PythonInterface
{
public:
PythonInterface();
virtual ~PythonInterface();
// Pass a caller tag (e.g. class name) so the main-thread assertion
// can report who triggered the first construction.
static PythonInterface & instance(const std::string & caller = "");
PythonInterface(const PythonInterface &) = delete;
PythonInterface & operator=(const PythonInterface &) = delete;
private:
explicit PythonInterface(const std::string & caller);
~PythonInterface();
pybind11::scoped_interpreter* guard_;
pybind11::gil_scoped_release* release_;
};
-6
View File
@@ -50,7 +50,6 @@ class Memory;
class BayesFilter;
class Signature;
class Optimizer;
class PythonInterface;
class RTABMAP_CORE_EXPORT Rtabmap
{
@@ -401,11 +400,6 @@ private:
Transform _pathTransformToGoal;
int _pathStuckCount;
float _pathStuckDistance;
#ifdef RTABMAP_PYTHON
PythonInterface * _python;
#endif
};
} // namespace rtabmap
+6 -3
View File
@@ -181,10 +181,13 @@ Rtabmap::Rtabmap() :
_pathTransformToGoal(Transform::getIdentity()),
_pathStuckCount(0),
_pathStuckDistance(0.0f)
#ifdef RTABMAP_PYTHON
,_python(new PythonInterface())
#endif
{
#ifdef RTABMAP_PYTHON
// Ensure the embedded Python interpreter is up. The first call here will
// assert that it runs on the main thread; callers building Rtabmap on a
// worker thread should construct the singleton in main() beforehand.
PythonInterface::instance("Rtabmap");
#endif
}
Rtabmap::~Rtabmap() {
+1
View File
@@ -23,6 +23,7 @@ PyDescriptor::PyDescriptor(
dim_(Parameters::defaultPyDescriptorDim())
{
UDEBUG("");
PythonInterface::instance("PyDescriptor");
this->parseParameters(parameters);
}
+1
View File
@@ -24,6 +24,7 @@ PyDetector::PyDetector(const ParametersMap & parameters) :
path_(Parameters::defaultPyDetectorPath()),
cuda_(Parameters::defaultPyDetectorCuda())
{
PythonInterface::instance("PyDetector");
this->parseParameters(parameters);
UDEBUG("path = %s", path_.c_str());
+1
View File
@@ -30,6 +30,7 @@ PyMatcher::PyMatcher(
iterations_(iterations),
cuda_(cuda)
{
PythonInterface::instance("PyMatcher");
path_ = uReplaceChar(pythonMatcherPath, '~', UDirectory::homeDir());
model_ = uReplaceChar(model, '~', UDirectory::homeDir());
UINFO("path = %s", path_.c_str());
+27 -2
View File
@@ -8,14 +8,39 @@
#include <rtabmap/core/PythonInterface.h>
#include <rtabmap/utilite/ULogger.h>
#include <rtabmap/utilite/UThread.h>
#include <rtabmap/utilite/UConversion.h>
#include <pybind11/embed.h>
#include <filesystem>
#include <thread>
namespace rtabmap {
PythonInterface::PythonInterface()
namespace {
// Captured when librtabmap_core is loaded. The dynamic loader runs static
// initializers on the main thread before main(), so this records the main
// thread id (as long as the library isn't dlopen'd from a worker thread).
const std::thread::id g_mainThreadId = std::this_thread::get_id();
}
PythonInterface & PythonInterface::instance(const std::string & caller)
{
UINFO("Initialize python interpreter");
// Meyers singleton: thread-safe construction in C++11, destroyed at exit.
// The constructor asserts it runs on the main thread; the caller tag
// from the first invocation is captured into the assertion message.
static PythonInterface inst(caller);
return inst;
}
PythonInterface::PythonInterface(const std::string & caller)
{
UASSERT_MSG(std::this_thread::get_id() == g_mainThreadId,
uFormat("PythonInterface must be created on the main thread "
"(first construction triggered by \"%s\"). Call "
"PythonInterface::instance() early in main() before "
"any worker thread touches a Python-backed class.",
caller.empty()?"<unspecified>":caller.c_str()).c_str());
UINFO("Initialize python interpreter (triggered by \"%s\")",
caller.empty()?"<unspecified>":caller.c_str());
guard_ = new pybind11::scoped_interpreter();
// Tell Python to look in this directory for DLLs