mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-16 04:11:13 +00:00
Fix/impl refactor (#14776)
* fix: impl refactor * fix: unload/load python module, race conditions, freezes * remove dead code * remove extra hook * remove more dead code * fix gil run script
This commit is contained in:
@@ -15,17 +15,77 @@
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
thread_local PythonInterpreter* PythonRuntimeLease::s_owner = nullptr;
|
||||
thread_local unsigned int PythonRuntimeLease::s_depth = 0;
|
||||
|
||||
PythonRuntimeLease::PythonRuntimeLease(PythonInterpreter& interpreter)
|
||||
{
|
||||
if (s_owner == &interpreter) {
|
||||
m_interpreter = &interpreter;
|
||||
++s_depth;
|
||||
return;
|
||||
}
|
||||
|
||||
m_lock = std::shared_lock<std::shared_mutex>(interpreter.m_runtime_mutex);
|
||||
if (!interpreter.m_initialized.load(std::memory_order_acquire)) {
|
||||
m_lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
m_interpreter = &interpreter;
|
||||
s_owner = &interpreter;
|
||||
s_depth = 1;
|
||||
}
|
||||
|
||||
PythonRuntimeLease::PythonRuntimeLease(PythonRuntimeLease&& other) noexcept
|
||||
: m_interpreter(other.m_interpreter), m_lock(std::move(other.m_lock))
|
||||
{
|
||||
other.m_interpreter = nullptr;
|
||||
}
|
||||
|
||||
PythonRuntimeLease& PythonRuntimeLease::operator=(PythonRuntimeLease&& other) noexcept
|
||||
{
|
||||
if (this != &other) {
|
||||
release();
|
||||
m_lock = std::move(other.m_lock);
|
||||
m_interpreter = other.m_interpreter;
|
||||
other.m_interpreter = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PythonRuntimeLease::release()
|
||||
{
|
||||
if (!m_interpreter)
|
||||
return;
|
||||
|
||||
if (s_owner == m_interpreter && s_depth > 0) {
|
||||
--s_depth;
|
||||
if (s_depth == 0)
|
||||
s_owner = nullptr;
|
||||
}
|
||||
m_interpreter = nullptr;
|
||||
}
|
||||
|
||||
PythonRuntimeLease::~PythonRuntimeLease()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void log_python_exception_keep(pybind11::error_already_set& err)
|
||||
{
|
||||
// The GIL may already be released here: the macro's gil_scoped_acquire is a
|
||||
// local destroyed by stack unwinding before this catch runs. Touching Python
|
||||
// state below needs the GIL. PyGILState_Ensure is reentrant — harmless if held.
|
||||
PythonGILState gil;
|
||||
if (!gil)
|
||||
return;
|
||||
|
||||
// Non-destructive: print the traceback to sys.stderr (tee'd to the session log)
|
||||
// WITHOUT consuming err, so the caller can rethrow it intact. For example, downstream C++
|
||||
@@ -108,8 +168,11 @@ std::string executable_name(const char* base)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool add_sys_path_entry(const boost::filesystem::path& path, std::string& error)
|
||||
bool add_sys_path_entry(const boost::filesystem::path& path, std::string& error, bool* inserted = nullptr)
|
||||
{
|
||||
if (inserted)
|
||||
*inserted = false;
|
||||
|
||||
PyObject* sys_path = PySys_GetObject("path");
|
||||
if (!sys_path || !PyList_Check(sys_path)) {
|
||||
error = "Python sys.path is not available";
|
||||
@@ -136,6 +199,9 @@ bool add_sys_path_entry(const boost::filesystem::path& path, std::string& error)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inserted)
|
||||
*inserted = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -387,94 +453,23 @@ std::string PythonInterpreter::bundled_uv_path()
|
||||
|
||||
bool PythonInterpreter::initialize()
|
||||
{
|
||||
if (m_initialized) {
|
||||
std::unique_lock<std::shared_mutex> runtime_lock(m_runtime_mutex);
|
||||
if (m_initialized.load(std::memory_order_acquire)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
m_last_error.clear();
|
||||
m_plugin_path_users.clear();
|
||||
m_plugin_path_owned.clear();
|
||||
m_plugin_module_users.clear();
|
||||
m_plugin_module_owned.clear();
|
||||
|
||||
try {
|
||||
// Set Python home to the bundled Python installation
|
||||
// This is critical for finding the standard library (encodings module, etc.)
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
std::string python_home;
|
||||
const auto valid_python_home = [](const fs::path& candidate) {
|
||||
#ifdef _WIN32
|
||||
return fs::exists(candidate / "Lib" / "encodings") &&
|
||||
(fs::exists(candidate / PYTHON_DLL) || fs::exists(candidate / PYTHON_DEBUG_DLL));
|
||||
#else
|
||||
return fs::exists(candidate / "lib" / PYTHON_STDLIB_DIR / "encodings");
|
||||
#endif
|
||||
};
|
||||
|
||||
// Determine Python home based on application structure
|
||||
// Python is bundled at different locations depending on platform and build type
|
||||
|
||||
// Strategy 1: Platform-specific bundled locations (highest priority)
|
||||
#ifdef __APPLE__
|
||||
// macOS app bundle: OrcaSlicer.app/Contents/MacOS/python
|
||||
// (resources_dir is Contents/Resources, so go up and into MacOS)
|
||||
fs::path bundle_python = fs::path(resources_dir()).parent_path() / "MacOS" / "python";
|
||||
if (valid_python_home(bundle_python)) {
|
||||
python_home = bundle_python.string();
|
||||
BOOST_LOG_TRIVIAL(info) << "Found Python in macOS app bundle: " << python_home;
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
fs::path exe_python = boost::dll::program_location().parent_path() / "python";
|
||||
if (valid_python_home(exe_python)) {
|
||||
python_home = exe_python.string();
|
||||
BOOST_LOG_TRIVIAL(info) << "Found Python next to Windows executable: " << python_home;
|
||||
}
|
||||
#else
|
||||
// Linux: typically in ../lib or ../share relative to binary
|
||||
fs::path linux_python = fs::path(resources_dir()).parent_path() / "lib" / "python";
|
||||
if (valid_python_home(linux_python)) {
|
||||
python_home = linux_python.string();
|
||||
BOOST_LOG_TRIVIAL(info) << "Found Python in Linux install: " << python_home;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Strategy 2: Configured development dependency directory.
|
||||
if (python_home.empty()) {
|
||||
fs::path configured_python = ORCA_BUNDLED_PYTHON_ROOT;
|
||||
if (!configured_python.empty() && valid_python_home(configured_python)) {
|
||||
python_home = configured_python.string();
|
||||
BOOST_LOG_TRIVIAL(info) << "Found Python in configured bundled path: " << python_home;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Development build directory from runtime environment.
|
||||
if (python_home.empty()) {
|
||||
const char* prefix_path = std::getenv("CMAKE_PREFIX_PATH");
|
||||
if (prefix_path && std::strlen(prefix_path) > 0) {
|
||||
fs::path libpython = fs::path(prefix_path) / "libpython";
|
||||
if (valid_python_home(libpython)) {
|
||||
python_home = libpython.string();
|
||||
BOOST_LOG_TRIVIAL(info) << "Found Python in CMAKE_PREFIX_PATH: " << python_home;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Check resources directory (alternate bundling location)
|
||||
if (python_home.empty()) {
|
||||
fs::path res_python = fs::path(resources_dir()) / "python";
|
||||
if (valid_python_home(res_python)) {
|
||||
python_home = res_python.string();
|
||||
BOOST_LOG_TRIVIAL(info) << "Found Python in resources directory: " << python_home;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 4: Check data_dir (user configuration directory)
|
||||
#ifndef _WIN32
|
||||
if (python_home.empty()) {
|
||||
fs::path data_python = fs::path(data_dir()) / "python";
|
||||
if (valid_python_home(data_python)) {
|
||||
python_home = data_python.string();
|
||||
BOOST_LOG_TRIVIAL(info) << "Found Python in data directory: " << python_home;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
const std::string python_home = find_bundled_python_home().string();
|
||||
|
||||
if (python_home.empty()) {
|
||||
m_last_error = "Could not locate bundled Python installation";
|
||||
@@ -493,6 +488,8 @@ bool PythonInterpreter::initialize()
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Found bundled Python home: " << python_home;
|
||||
|
||||
// Verify Python standard library directory exists
|
||||
#ifdef _WIN32
|
||||
fs::path python_lib = fs::path(python_home) / "Lib";
|
||||
@@ -539,6 +536,10 @@ bool PythonInterpreter::initialize()
|
||||
// Set Python home - this is the prefix where Python libraries are located
|
||||
PyConfig config;
|
||||
PyConfig_InitPythonConfig(&config);
|
||||
// Do not let the host process's PYTHONPATH or user site-packages override the bundled
|
||||
// runtime used by plugins.
|
||||
config.use_environment = 0;
|
||||
config.user_site_directory = 0;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Calling PyConfig_SetBytesString with home=" << python_home;
|
||||
PyStatus status = PyConfig_SetBytesString(&config, &config.home, python_home.c_str());
|
||||
@@ -631,11 +632,10 @@ bool PythonInterpreter::initialize()
|
||||
// background-thread exceptions) to <data_dir>/log/python_*.log.
|
||||
install_python_stderr_redirect();
|
||||
|
||||
m_initialized = true;
|
||||
|
||||
// Release the GIL so other threads can acquire it via PyGILState_Ensure.
|
||||
// Without this, calls from background threads will block trying to acquire the GIL.
|
||||
m_main_thread_state = PyEval_SaveThread();
|
||||
m_initialized.store(true, std::memory_order_release);
|
||||
BOOST_LOG_TRIVIAL(debug) << "Main thread released Python GIL after initialization";
|
||||
return true;
|
||||
|
||||
@@ -650,7 +650,8 @@ PythonInterpreter::~PythonInterpreter() { shutdown(); }
|
||||
|
||||
void PythonInterpreter::shutdown()
|
||||
{
|
||||
if (!m_initialized)
|
||||
std::unique_lock<std::shared_mutex> runtime_lock(m_runtime_mutex);
|
||||
if (!m_initialized.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Python interpreter shutdown enter";
|
||||
@@ -668,80 +669,305 @@ void PythonInterpreter::shutdown()
|
||||
m_interpreter.reset();
|
||||
BOOST_LOG_TRIVIAL(info) << "Python interpreter finalized";
|
||||
|
||||
m_initialized = false;
|
||||
m_plugin_path_users.clear();
|
||||
m_plugin_path_owned.clear();
|
||||
m_plugin_module_users.clear();
|
||||
m_plugin_module_owned.clear();
|
||||
m_initialized.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool PythonInterpreter::add_sys_path(const std::string& path, std::string& error)
|
||||
bool PythonInterpreter::add_plugin_sys_path(const std::string& path, std::string& error)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
if (!m_initialized.load(std::memory_order_acquire)) {
|
||||
error = "Python interpreter not initialized";
|
||||
return false;
|
||||
}
|
||||
|
||||
PythonGILState gil;
|
||||
if (!gil) {
|
||||
error = "Python interpreter is shutting down";
|
||||
return false;
|
||||
}
|
||||
|
||||
return add_plugin_sys_path_locked(path, error);
|
||||
}
|
||||
|
||||
bool PythonInterpreter::add_plugin_sys_path_locked(const std::string& path, std::string& error)
|
||||
{
|
||||
bool inserted = false;
|
||||
if (!add_sys_path_entry(boost::filesystem::path(path), error, &inserted))
|
||||
return false;
|
||||
|
||||
auto users = m_plugin_path_users.find(path);
|
||||
if (users == m_plugin_path_users.end()) {
|
||||
m_plugin_path_owned[path] = inserted;
|
||||
m_plugin_path_users.emplace(path, 1);
|
||||
} else {
|
||||
++users->second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PythonInterpreter::remove_plugin_sys_paths_locked(const std::vector<std::string>& paths)
|
||||
{
|
||||
PyObject* sys_path = PySys_GetObject("path");
|
||||
if (!sys_path || !PyList_Check(sys_path)) {
|
||||
error = "Python sys.path is not available";
|
||||
return false;
|
||||
PyErr_Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
PyObjectPtr py_path(PyUnicode_DecodeFSDefault(path.c_str()));
|
||||
if (!py_path) {
|
||||
error = "Failed to decode path for Python sys.path: " + path;
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
for (const std::string& path : paths) {
|
||||
auto users = m_plugin_path_users.find(path);
|
||||
if (users == m_plugin_path_users.end())
|
||||
continue;
|
||||
|
||||
if (--users->second != 0)
|
||||
continue;
|
||||
|
||||
const bool owned = m_plugin_path_owned[path];
|
||||
m_plugin_path_users.erase(users);
|
||||
m_plugin_path_owned.erase(path);
|
||||
if (!owned)
|
||||
continue;
|
||||
|
||||
PyObjectPtr py_path(PyUnicode_DecodeFSDefault(path.c_str()));
|
||||
if (!py_path) {
|
||||
PyErr_Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Py_ssize_t index = PyList_Size(sys_path) - 1; index >= 0; --index) {
|
||||
PyObject* entry = PyList_GetItem(sys_path, index); // borrowed reference
|
||||
int equal = entry ? PyObject_RichCompareBool(entry, py_path.get(), Py_EQ) : 0;
|
||||
if (equal == 1 && PySequence_DelItem(sys_path, index) != 0)
|
||||
PyErr_Clear();
|
||||
else if (equal < 0)
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
const int contains = PySequence_Contains(sys_path, py_path.get());
|
||||
if (contains == 1)
|
||||
return true;
|
||||
if (contains < 0)
|
||||
PyErr_Clear();
|
||||
|
||||
if (PyList_Insert(sys_path, 0, py_path.get()) != 0) {
|
||||
error = "Failed to append path to Python sys.path: " + path;
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PythonInterpreter::execute_string(const std::string& code, std::string& error)
|
||||
void PythonInterpreter::record_plugin_modules_locked(const std::string& module_name,
|
||||
const std::vector<std::string>& plugin_paths,
|
||||
std::vector<std::string>* plugin_modules)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
error = "Python interpreter not initialized";
|
||||
return false;
|
||||
if (!plugin_modules)
|
||||
return;
|
||||
|
||||
PyObject* modules = PyImport_GetModuleDict();
|
||||
if (!modules)
|
||||
return;
|
||||
|
||||
PyObjectPtr names(PyDict_Keys(modules));
|
||||
if (!names) {
|
||||
PyErr_Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string prefix = module_name + ".";
|
||||
const auto path_matches = [](const std::string& file, const std::string& root) {
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
boost::system::error_code ec;
|
||||
fs::path file_path = fs::weakly_canonical(fs::path(file), ec);
|
||||
if (ec) {
|
||||
ec.clear();
|
||||
file_path = fs::absolute(fs::path(file), ec);
|
||||
}
|
||||
if (ec)
|
||||
return false;
|
||||
|
||||
ec.clear();
|
||||
fs::path root_path = fs::weakly_canonical(fs::path(root), ec);
|
||||
if (ec) {
|
||||
ec.clear();
|
||||
root_path = fs::absolute(fs::path(root), ec);
|
||||
}
|
||||
if (ec)
|
||||
return false;
|
||||
|
||||
const std::string file_string = file_path.generic_string();
|
||||
std::string root_string = root_path.generic_string();
|
||||
if (root_string.empty())
|
||||
return false;
|
||||
if (root_string.back() != '/')
|
||||
root_string.push_back('/');
|
||||
return file_string.compare(0, root_string.size(), root_string) == 0;
|
||||
};
|
||||
|
||||
const Py_ssize_t count = PyList_Size(names.get());
|
||||
for (Py_ssize_t index = 0; index < count; ++index) {
|
||||
PyObject* name_obj = PyList_GetItem(names.get(), index); // borrowed reference
|
||||
if (!name_obj || !PyUnicode_Check(name_obj))
|
||||
continue;
|
||||
|
||||
const char* name_utf8 = PyUnicode_AsUTF8(name_obj);
|
||||
if (!name_utf8) {
|
||||
PyErr_Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string name(name_utf8);
|
||||
const bool is_plugin_namespace = name == module_name || name.compare(0, prefix.size(), prefix) == 0;
|
||||
bool is_plugin_path_module = false;
|
||||
if (!is_plugin_namespace) {
|
||||
PyObject* module = PyDict_GetItem(modules, name_obj); // borrowed reference
|
||||
if (!module)
|
||||
continue;
|
||||
|
||||
PyObjectPtr file(PyObject_GetAttrString(module, "__file__"));
|
||||
if (file && PyUnicode_Check(file.get())) {
|
||||
const char* file_utf8 = PyUnicode_AsUTF8(file.get());
|
||||
if (file_utf8) {
|
||||
for (const std::string& path : plugin_paths) {
|
||||
if (path_matches(file_utf8, path)) {
|
||||
is_plugin_path_module = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PyErr_Clear();
|
||||
}
|
||||
} else {
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
if (!is_plugin_path_module) {
|
||||
PyObjectPtr package_path(PyObject_GetAttrString(module, "__path__"));
|
||||
if (package_path) {
|
||||
const Py_ssize_t path_count = PySequence_Size(package_path.get());
|
||||
if (path_count < 0) {
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
for (Py_ssize_t path_index = 0; path_index < path_count && !is_plugin_path_module; ++path_index) {
|
||||
PyObjectPtr path_entry(PySequence_GetItem(package_path.get(), path_index));
|
||||
if (!path_entry) {
|
||||
PyErr_Clear();
|
||||
continue;
|
||||
}
|
||||
if (!PyUnicode_Check(path_entry.get()))
|
||||
continue;
|
||||
const char* path_utf8 = PyUnicode_AsUTF8(path_entry.get());
|
||||
if (!path_utf8) {
|
||||
PyErr_Clear();
|
||||
continue;
|
||||
}
|
||||
for (const std::string& path : plugin_paths) {
|
||||
if (path_matches(path_utf8, path)) {
|
||||
is_plugin_path_module = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_plugin_namespace && !is_plugin_path_module)
|
||||
continue;
|
||||
|
||||
auto users = m_plugin_module_users.find(name);
|
||||
if (users == m_plugin_module_users.end()) {
|
||||
m_plugin_module_owned[name] = true;
|
||||
m_plugin_module_users.emplace(name, 1);
|
||||
} else {
|
||||
++users->second;
|
||||
}
|
||||
plugin_modules->push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
void PythonInterpreter::remove_plugin_modules_locked(const std::vector<std::string>& plugin_modules)
|
||||
{
|
||||
PyObject* modules = PyImport_GetModuleDict();
|
||||
if (!modules)
|
||||
return;
|
||||
|
||||
for (const std::string& name : plugin_modules) {
|
||||
auto users = m_plugin_module_users.find(name);
|
||||
if (users == m_plugin_module_users.end())
|
||||
continue;
|
||||
|
||||
if (--users->second != 0)
|
||||
continue;
|
||||
|
||||
const bool owned = m_plugin_module_owned[name];
|
||||
m_plugin_module_users.erase(users);
|
||||
m_plugin_module_owned.erase(name);
|
||||
if (owned && PyDict_DelItemString(modules, name.c_str()) != 0)
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void PythonInterpreter::remove_module_tree_locked(const std::string& module_name)
|
||||
{
|
||||
if (module_name.empty())
|
||||
return;
|
||||
|
||||
PyObject* modules = PyImport_GetModuleDict();
|
||||
if (!modules)
|
||||
return;
|
||||
|
||||
PyObjectPtr names(PyDict_Keys(modules));
|
||||
if (!names) {
|
||||
PyErr_Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string prefix = module_name + ".";
|
||||
const Py_ssize_t count = PyList_Size(names.get());
|
||||
for (Py_ssize_t index = 0; index < count; ++index) {
|
||||
PyObject* name_obj = PyList_GetItem(names.get(), index); // borrowed reference
|
||||
if (!name_obj || !PyUnicode_Check(name_obj))
|
||||
continue;
|
||||
|
||||
const char* name_utf8 = PyUnicode_AsUTF8(name_obj);
|
||||
if (!name_utf8) {
|
||||
PyErr_Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string name(name_utf8);
|
||||
if (name != module_name && name.compare(0, prefix.size(), prefix) != 0)
|
||||
continue;
|
||||
|
||||
if (PyDict_DelItem(modules, name_obj) != 0)
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void PythonInterpreter::unload_module(PyObject* module,
|
||||
const std::string& module_name,
|
||||
const std::vector<std::string>& plugin_paths,
|
||||
const std::vector<std::string>& plugin_modules)
|
||||
{
|
||||
if (!module && plugin_paths.empty() && plugin_modules.empty())
|
||||
return;
|
||||
|
||||
if (!m_initialized.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
PythonGILState gil;
|
||||
if (!gil)
|
||||
return;
|
||||
|
||||
PyObject* main_module = PyImport_AddModule("__main__");
|
||||
if (!main_module) {
|
||||
error = "Failed to get __main__ module";
|
||||
return false;
|
||||
}
|
||||
|
||||
PyObject* global_dict = PyModule_GetDict(main_module);
|
||||
PyObjectPtr result(PyRun_String(code.c_str(), Py_file_input, global_dict, global_dict));
|
||||
|
||||
if (!result) {
|
||||
PyObject *ptype, *pvalue, *ptraceback;
|
||||
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
|
||||
error = format_python_error(ptype, pvalue, ptraceback);
|
||||
Py_XDECREF(ptype);
|
||||
Py_XDECREF(pvalue);
|
||||
Py_XDECREF(ptraceback);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
remove_plugin_modules_locked(plugin_modules);
|
||||
if (plugin_modules.empty())
|
||||
remove_module_tree_locked(module_name);
|
||||
remove_plugin_sys_paths_locked(plugin_paths);
|
||||
Py_XDECREF(module);
|
||||
}
|
||||
|
||||
PyObject* PythonInterpreter::load_module_from_file(const std::string& file_path, std::string& error)
|
||||
PyObject* PythonInterpreter::load_module_from_file(const std::string& file_path,
|
||||
std::string& error,
|
||||
std::vector<std::string>* plugin_paths,
|
||||
std::vector<std::string>* plugin_modules)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
if (!m_initialized.load(std::memory_order_acquire)) {
|
||||
error = "Python interpreter not initialized";
|
||||
return nullptr;
|
||||
}
|
||||
@@ -755,49 +981,27 @@ PyObject* PythonInterpreter::load_module_from_file(const std::string& file_path,
|
||||
}
|
||||
|
||||
PythonGILState gil;
|
||||
if (!gil) {
|
||||
error = "Python interpreter is shutting down";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Add the directory to sys.path
|
||||
fs::path dir_path = path.parent_path();
|
||||
std::string module_name = path.stem().string();
|
||||
|
||||
PyObjectPtr sys(PyImport_ImportModule("sys"));
|
||||
if (!sys) {
|
||||
error = "Failed to import sys module";
|
||||
const std::string dir = dir_path.string();
|
||||
if (!add_plugin_sys_path_locked(dir, error))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObject* sys_path = PyObject_GetAttrString(sys.get(), "path");
|
||||
if (!sys_path) {
|
||||
error = "Failed to get sys.path";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObjectPtr dir_str(PyUnicode_FromString(dir_path.string().c_str()));
|
||||
if (!dir_str) {
|
||||
Py_DECREF(sys_path);
|
||||
error = "Failed to create directory string";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (PyList_Insert(sys_path, 0, dir_str.get()) < 0) {
|
||||
Py_DECREF(sys_path);
|
||||
error = "Failed to add directory to sys.path";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Py_DECREF(sys_path);
|
||||
if (plugin_paths)
|
||||
plugin_paths->push_back(dir);
|
||||
|
||||
// Ensure module is re-imported fresh by removing any cached instance.
|
||||
if (PyObject* modules = PyImport_GetModuleDict()) {
|
||||
if (PyDict_GetItemString(modules, module_name.c_str())) {
|
||||
if (PyDict_DelItemString(modules, module_name.c_str()) != 0) {
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
remove_module_tree_locked(module_name);
|
||||
|
||||
// Import the module
|
||||
PyObject* module = PyImport_ImportModule(module_name.c_str());
|
||||
record_plugin_modules_locked(module_name, plugin_paths ? *plugin_paths : std::vector<std::string>{}, plugin_modules);
|
||||
if (!module) {
|
||||
PyObject *ptype, *pvalue, *ptraceback;
|
||||
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
|
||||
@@ -811,9 +1015,13 @@ PyObject* PythonInterpreter::load_module_from_file(const std::string& file_path,
|
||||
return module;
|
||||
}
|
||||
|
||||
PyObject* PythonInterpreter::load_module_from_directory(const std::string& dir_path, const std::string& pkg_name, std::string& error)
|
||||
PyObject* PythonInterpreter::load_module_from_directory(const std::string& dir_path,
|
||||
const std::string& pkg_name,
|
||||
std::string& error,
|
||||
std::vector<std::string>* plugin_paths,
|
||||
std::vector<std::string>* plugin_modules)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
if (!m_initialized.load(std::memory_order_acquire)) {
|
||||
error = "Python interpreter not initialized";
|
||||
return nullptr;
|
||||
}
|
||||
@@ -827,43 +1035,21 @@ PyObject* PythonInterpreter::load_module_from_directory(const std::string& dir_p
|
||||
}
|
||||
|
||||
PythonGILState gil;
|
||||
|
||||
PyObjectPtr sys(PyImport_ImportModule("sys"));
|
||||
if (!sys) {
|
||||
error = "Failed to import sys module";
|
||||
if (!gil) {
|
||||
error = "Python interpreter is shutting down";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObject* sys_path = PyObject_GetAttrString(sys.get(), "path");
|
||||
if (!sys_path) {
|
||||
error = "Failed to get sys.path";
|
||||
const std::string directory = dir.string();
|
||||
if (!add_plugin_sys_path_locked(directory, error))
|
||||
return nullptr;
|
||||
}
|
||||
if (plugin_paths)
|
||||
plugin_paths->push_back(directory);
|
||||
|
||||
PyObjectPtr dir_str(PyUnicode_FromString(dir.string().c_str()));
|
||||
if (!dir_str) {
|
||||
Py_DECREF(sys_path);
|
||||
error = "Failed to create directory string";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (PyList_Insert(sys_path, 0, dir_str.get()) < 0) {
|
||||
Py_DECREF(sys_path);
|
||||
error = "Failed to add directory to sys.path";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Py_DECREF(sys_path);
|
||||
|
||||
if (PyObject* modules = PyImport_GetModuleDict()) {
|
||||
if (PyDict_GetItemString(modules, pkg_name.c_str())) {
|
||||
if (PyDict_DelItemString(modules, pkg_name.c_str()) != 0) {
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
remove_module_tree_locked(pkg_name);
|
||||
|
||||
PyObject* module = PyImport_ImportModule(pkg_name.c_str());
|
||||
record_plugin_modules_locked(pkg_name, plugin_paths ? *plugin_paths : std::vector<std::string>{}, plugin_modules);
|
||||
if (!module) {
|
||||
PyObject *ptype, *pvalue, *ptraceback;
|
||||
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
|
||||
@@ -877,9 +1063,13 @@ PyObject* PythonInterpreter::load_module_from_directory(const std::string& dir_p
|
||||
return module;
|
||||
}
|
||||
|
||||
PyObject* PythonInterpreter::load_module_from_whl(const std::string& file_path, const std::string& pkg_name, std::string& error)
|
||||
PyObject* PythonInterpreter::load_module_from_whl(const std::string& file_path,
|
||||
const std::string& pkg_name,
|
||||
std::string& error,
|
||||
std::vector<std::string>* plugin_paths,
|
||||
std::vector<std::string>* plugin_modules)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
if (!m_initialized.load(std::memory_order_acquire)) {
|
||||
error = "Python interpreter not initialized";
|
||||
return nullptr;
|
||||
}
|
||||
@@ -900,98 +1090,7 @@ PyObject* PythonInterpreter::load_module_from_whl(const std::string& file_path,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return load_module_from_directory(extract_dir.string(), pkg_name, error);
|
||||
}
|
||||
|
||||
bool PythonInterpreter::call_function(
|
||||
PyObject* module, const std::string& function_name, const std::string& arg, std::string& result, std::string& error)
|
||||
{
|
||||
if (!m_initialized || !module) {
|
||||
error = "Python interpreter not initialized or module is null";
|
||||
return false;
|
||||
}
|
||||
|
||||
PythonGILState gil;
|
||||
|
||||
PyObject* func = PyObject_GetAttrString(module, function_name.c_str());
|
||||
if (!func || !PyCallable_Check(func)) {
|
||||
Py_XDECREF(func);
|
||||
error = "Function '" + function_name + "' not found or not callable";
|
||||
return false;
|
||||
}
|
||||
|
||||
PyObjectPtr args(PyTuple_New(1));
|
||||
PyObjectPtr arg_str(PyUnicode_FromString(arg.c_str()));
|
||||
PyTuple_SetItem(args.get(), 0, arg_str.release());
|
||||
|
||||
PyObjectPtr py_result(PyObject_CallObject(func, args.get()));
|
||||
Py_DECREF(func);
|
||||
|
||||
if (!py_result) {
|
||||
PyObject *ptype, *pvalue, *ptraceback;
|
||||
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
|
||||
error = "Function call failed: " + format_python_error(ptype, pvalue, ptraceback);
|
||||
Py_XDECREF(ptype);
|
||||
Py_XDECREF(pvalue);
|
||||
Py_XDECREF(ptraceback);
|
||||
return false;
|
||||
}
|
||||
|
||||
result = py_object_to_string(py_result.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PythonInterpreter::call_function_no_args(PyObject* module, const std::string& function_name, std::string& result, std::string& error)
|
||||
{
|
||||
if (!m_initialized || !module) {
|
||||
error = "Python interpreter not initialized or module is null";
|
||||
return false;
|
||||
}
|
||||
|
||||
PythonGILState gil;
|
||||
|
||||
PyObject* func = PyObject_GetAttrString(module, function_name.c_str());
|
||||
if (!func || !PyCallable_Check(func)) {
|
||||
Py_XDECREF(func);
|
||||
error = "Function '" + function_name + "' not found or not callable";
|
||||
return false;
|
||||
}
|
||||
|
||||
PyObjectPtr py_result(PyObject_CallObject(func, nullptr));
|
||||
Py_DECREF(func);
|
||||
|
||||
if (!py_result) {
|
||||
PyObject *ptype, *pvalue, *ptraceback;
|
||||
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
|
||||
error = "Function call failed: " + format_python_error(ptype, pvalue, ptraceback);
|
||||
Py_XDECREF(ptype);
|
||||
Py_XDECREF(pvalue);
|
||||
Py_XDECREF(ptraceback);
|
||||
return false;
|
||||
}
|
||||
|
||||
result = py_object_to_string(py_result.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string PythonInterpreter::py_object_to_string(PyObject* obj)
|
||||
{
|
||||
if (!obj) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (PyUnicode_Check(obj)) {
|
||||
const char* str = PyUnicode_AsUTF8(obj);
|
||||
return str ? std::string(str) : "";
|
||||
}
|
||||
|
||||
PyObjectPtr str_obj(PyObject_Str(obj));
|
||||
if (str_obj) {
|
||||
const char* str = PyUnicode_AsUTF8(str_obj.get());
|
||||
return str ? std::string(str) : "";
|
||||
}
|
||||
|
||||
return "";
|
||||
return load_module_from_directory(extract_dir.string(), pkg_name, error, plugin_paths, plugin_modules);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
Reference in New Issue
Block a user