Mercurial > hg > orthanc-python
view Sources/Plugin.cpp @ 57:46fe70776d61
Fix possible deadlock with "orthanc.RegisterOnChangeCallback()"
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 21 Jan 2021 18:27:06 +0100 |
parents | 23f3099bed47 |
children | 32de70a1e4c7 |
line wrap: on
line source
/** * Python plugin for Orthanc * Copyright (C) 2020-2021 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ // http://edcjones.tripod.com/refcount.html // https://docs.python.org/3/extending/extending.html // https://www.codevate.com/blog/7-concurrency-with-embedded-python-in-a-multi-threaded-c-application // https://fr.slideshare.net/YiLungTsai/embed-python #include "IncomingHttpRequestFilter.h" #include "OnChangeCallback.h" #include "OnStoredInstanceCallback.h" #include "RestCallbacks.h" #include "PythonModule.h" #include "Autogenerated/sdk.h" #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" #include <boost/algorithm/string/predicate.hpp> #include <boost/filesystem.hpp> // The "dl_iterate_phdr()" function (to walk through shared libraries) // is not available on Microsoft Windows and Apple OS X #if defined(_WIN32) # define HAS_DL_ITERATE 0 #elif defined(__APPLE__) && defined(__MACH__) # define HAS_DL_ITERATE 0 #else # define HAS_DL_ITERATE 1 #endif static bool pythonEnabled_ = false; static std::string userScriptName_; static std::vector<PyMethodDef> globalFunctions_; static void InstallClasses(PyObject* module) { RegisterOrthancSdk(module); } static void SetupGlobalFunctions() { if (!globalFunctions_.empty()) { ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); } /** * Add all the manual global functions **/ std::list<PyMethodDef> functions; { PyMethodDef f = { "RegisterRestCallback", RegisterRestCallback, METH_VARARGS, "" }; functions.push_back(f); } { PyMethodDef f = { "RegisterOnChangeCallback", RegisterOnChangeCallback, METH_VARARGS, "" }; functions.push_back(f); } { PyMethodDef f = { "RegisterOnStoredInstanceCallback", RegisterOnStoredInstanceCallback, METH_VARARGS, "" }; functions.push_back(f); } { // New in release 3.0 PyMethodDef f = { "RegisterIncomingHttpRequestFilter", RegisterIncomingHttpRequestFilter, METH_VARARGS, "" }; functions.push_back(f); } /** * Append all the global functions that were automatically generated **/ const PyMethodDef* sdk = GetOrthancSdkFunctions(); for (size_t i = 0; sdk[i].ml_name != NULL; i++) { functions.push_back(sdk[i]); } /** * Flatten the list of functions into the vector **/ globalFunctions_.resize(functions.size()); std::copy(functions.begin(), functions.end(), globalFunctions_.begin()); PyMethodDef sentinel = { NULL }; globalFunctions_.push_back(sentinel); } static PyMethodDef* GetGlobalFunctions() { if (globalFunctions_.empty()) { // "SetupGlobalFunctions()" should have been called ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); } else { return &globalFunctions_[0]; } } #if HAS_DL_ITERATE == 1 #include <dlfcn.h> #include <link.h> // For dl_phdr_info static int ForceImportCallback(struct dl_phdr_info *info, size_t size, void *data) { /** * The following line solves the error: "ImportError: * /usr/lib/python2.7/dist-packages/PIL/_imaging.x86_64-linux-gnu.so: * undefined symbol: PyExc_SystemError" * https://stackoverflow.com/a/48517485/881731 * * dlopen("/usr/lib/x86_64-linux-gnu/libpython2.7.so", RTLD_NOW | RTLD_LAZY | RTLD_GLOBAL); * * Another fix consists in using LD_PRELOAD as follows: * LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libpython2.7.so ~/Subversion/orthanc/i/Orthanc tutu.json **/ std::string module(info->dlpi_name); if (module.find("python") != std::string::npos) { OrthancPlugins::LogWarning("Force global loading of Python shared library: " + module); dlopen(module.c_str(), RTLD_NOW | RTLD_LAZY | RTLD_GLOBAL); } return 0; } #endif extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) { OrthancPlugins::SetGlobalContext(c); OrthancPlugins::LogWarning("Python plugin is initializing"); /* Check the version of the Orthanc core */ if (OrthancPluginCheckVersion(c) == 0) { char info[1024]; sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", c->orthancVersion, ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); OrthancPluginLogError(c, info); return -1; } OrthancPluginSetDescription(c, "Run Python scripts as Orthanc plugins"); try { /** * Detection of the user script **/ OrthancPlugins::OrthancConfiguration config; static const char* const OPTION = "PythonScript"; std::string script; if (!config.LookupStringValue(script, OPTION)) { pythonEnabled_ = false; OrthancPlugins::LogWarning("The option \"" + std::string(OPTION) + "\" is not provided: " + "Python scripting is disabled"); } else { pythonEnabled_ = true; /** * Installation of the user script **/ const boost::filesystem::path path(script); if (!boost::iequals(path.extension().string(), ".py")) { OrthancPlugins::LogError("Python script must have the \".py\" file extension: " + path.string()); return -1; } if (!boost::filesystem::is_regular_file(path)) { OrthancPlugins::LogError("Inexistent directory for the Python script: " + path.string()); return -1; } boost::filesystem::path userScriptDirectory = boost::filesystem::absolute(path).parent_path(); { boost::filesystem::path module = path.filename().replace_extension(""); userScriptName_ = module.string(); } OrthancPlugins::LogWarning("Using Python script \"" + userScriptName_ + ".py\" from directory: " + userScriptDirectory.string()); /** * Initialization of Python **/ #if HAS_DL_ITERATE == 1 dl_iterate_phdr(ForceImportCallback, NULL); #endif SetupGlobalFunctions(); PythonLock::GlobalInitialize("orthanc", "Exception", GetGlobalFunctions, InstallClasses, config.GetBooleanValue("PythonVerbose", false)); PythonLock::AddSysPath(userScriptDirectory.string()); /** * Force loading the declarations in the user script **/ PythonLock lock; { PythonModule module(lock, userScriptName_); } std::string traceback; if (lock.HasErrorOccurred(traceback)) { OrthancPlugins::LogError("Error during the installation of the Python script, " "traceback:\n" + traceback); return -1; } } } catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) { OrthancPlugins::LogError("Exception while starting the Python plugin: " + std::string(e.What(c))); return -1; } return 0; } ORTHANC_PLUGINS_API void OrthancPluginFinalize() { OrthancPlugins::LogWarning("Python plugin is finalizing"); if (pythonEnabled_) { FinalizeOnChangeCallback(); FinalizeRestCallbacks(); FinalizeOnStoredInstanceCallback(); FinalizeIncomingHttpRequestFilter(); PythonLock::GlobalFinalize(); } } ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { return "python"; } ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() { return PLUGIN_VERSION; } }