From 3f0321f195abdbb89580ba10b522a500e205924d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 9 Jan 2021 02:36:11 +0100 Subject: [PATCH] Don't crash when a python plugin fails to load --- .../integrations/pythonintegrationplugin.cpp | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/libnymea-core/integrations/pythonintegrationplugin.cpp b/libnymea-core/integrations/pythonintegrationplugin.cpp index 4bcec22a..5bb711f2 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.cpp +++ b/libnymea-core/integrations/pythonintegrationplugin.cpp @@ -201,14 +201,16 @@ PythonIntegrationPlugin::~PythonIntegrationPlugin() // Acquire GIL for this plugin's interpreter PyEval_RestoreThread(m_threadState); - while (!m_runningTasks.isEmpty()) { - QFutureWatcher *watcher = m_runningTasks.keys().first(); - QString function = m_runningTasks.value(watcher); + if (m_pluginModule) { + while (!m_runningTasks.isEmpty()) { + QFutureWatcher *watcher = m_runningTasks.keys().first(); + QString function = m_runningTasks.value(watcher); - Py_BEGIN_ALLOW_THREADS - qCDebug(dcPythonIntegrations()) << "Waiting for" << metadata().pluginName() << "to finish" << function; - watcher->waitForFinished(); - Py_END_ALLOW_THREADS + Py_BEGIN_ALLOW_THREADS + qCDebug(dcPythonIntegrations()) << "Waiting for" << metadata().pluginName() << "to finish" << function; + watcher->waitForFinished(); + Py_END_ALLOW_THREADS + } } s_plugins.take(this); @@ -254,25 +256,6 @@ void PythonIntegrationPlugin::deinitPython() bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) { - QFileInfo fi(scriptFile); - - QFile metaDataFile(fi.absolutePath() + "/" + fi.baseName() + ".json"); - if (!metaDataFile.open(QFile::ReadOnly)) { - qCWarning(dcPythonIntegrations()) << "Error opening metadata file:" << metaDataFile.fileName(); - return false; - } - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(metaDataFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) { - qCWarning(dcPythonIntegrations()) << "Error parsing metadata file:" << error.errorString(); - return false; - } - setMetaData(PluginMetadata(jsonDoc.object())); - if (!metadata().isValid()) { - qCWarning(dcPythonIntegrations()) << "Plugin metadata not valid for plugin:" << scriptFile; - return false; - } - // Grab the main thread context and GIL PyEval_RestoreThread(s_mainThreadState); @@ -285,6 +268,32 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) // Import nymea module into this interpreter m_nymeaModule = PyImport_ImportModule("nymea"); + QFileInfo fi(scriptFile); + + QFile metaDataFile(fi.absolutePath() + "/" + fi.baseName() + ".json"); + if (!metaDataFile.open(QFile::ReadOnly)) { + qCWarning(dcPythonIntegrations()) << "Error opening metadata file:" << metaDataFile.fileName(); + PyEval_ReleaseThread(m_threadState); + return false; + } + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(metaDataFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcPythonIntegrations()) << "Error parsing metadata file:" << error.errorString(); + PyEval_ReleaseThread(m_threadState); + return false; + } + PluginMetadata metadata(jsonDoc.object()); + if (!metadata.isValid()) { + qCWarning(dcPythonIntegrations()) << "Plugin metadata not valid for plugin:" << scriptFile; + foreach (const QString &error, metadata.validationErrors()) { + qCWarning(dcThingManager()) << error; + } + PyEval_ReleaseThread(m_threadState); + return false; + } + setMetaData(metadata); + // Set up import path for the plugin directory // We intentionally strip site-packages and dist-packages because // that's too unpredictive in distribution. Instead all dependencies @@ -323,7 +332,7 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) s_plugins.insert(this, m_pluginModule); // Set up logger with appropriate logging category - QString category = metadata().pluginName(); + QString category = metadata.pluginName(); category.replace(0, 1, category[0].toUpper()); PyObject *args = Py_BuildValue("(s)", category.toUtf8().data()); PyNymeaLoggingHandler *logger = reinterpret_cast(PyObject_CallObject((PyObject*)&PyNymeaLoggingHandlerType, args)); @@ -364,7 +373,7 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) // Plugins can still spawn more threads on their own if the need to but have to manage them on their own. m_threadPool = new QThreadPool(this); m_threadPool->setMaxThreadCount(2); - qCDebug(dcPythonIntegrations()) << "Created a thread pool with a maximum of" << m_threadPool->maxThreadCount() << "threads for python plugin" << metadata().pluginName(); + qCDebug(dcPythonIntegrations()) << "Created a thread pool with a maximum of" << m_threadPool->maxThreadCount() << "threads for python plugin" << metadata.pluginName(); PyEval_ReleaseThread(m_threadState);