Blob Blame History Raw
From 5f61c5be95bb44f2ff3b913678c7bfdd3196ff69 Mon Sep 17 00:00:00 2001
From: dnpwwo <kendel.boul@gmail.com>
Date: Sun, 1 Jul 2018 13:29:03 +1000
Subject: [PATCH] Initial version working with Python 3.7

---
 hardware/plugins/DelayedLink.h     | 15 +++++++++++++++
 hardware/plugins/PluginManager.cpp | 11 ++++++++++-
 hardware/plugins/PluginManager.h   |  5 +++--
 hardware/plugins/PluginMessages.h  |  3 +++
 hardware/plugins/Plugins.cpp       | 14 +++++++++++++-
 hardware/plugins/Plugins.h         |  1 +
 main/EventsPythonModule.cpp        | 24 +++++++++++++++++-------
 7 files changed, 62 insertions(+), 11 deletions(-)

diff --git a/hardware/plugins/DelayedLink.h b/hardware/plugins/DelayedLink.h
index ecc7ec579..de9327a13 100644
--- a/hardware/plugins/DelayedLink.h
+++ b/hardware/plugins/DelayedLink.h
@@ -86,8 +86,13 @@ namespace Plugins {
 		DECLARE_PYTHON_SYMBOL(PyObject*, PyImport_ImportModule, const char*);
 		DECLARE_PYTHON_SYMBOL(PyObject*, PyObject_CallObject, PyObject* COMMA PyObject*);
 		DECLARE_PYTHON_SYMBOL(int, PyFrame_GetLineNumber, PyFrameObject*);
+		DECLARE_PYTHON_SYMBOL(void, PyEval_InitThreads, );
+		DECLARE_PYTHON_SYMBOL(PyThreadState*, PyThreadState_Get, );
 		DECLARE_PYTHON_SYMBOL(PyThreadState*, PyEval_SaveThread, void);
 		DECLARE_PYTHON_SYMBOL(void, PyEval_RestoreThread, PyThreadState*);
+		DECLARE_PYTHON_SYMBOL(void, PyEval_ReleaseLock, );
+		DECLARE_PYTHON_SYMBOL(PyThreadState*, PyThreadState_Swap, PyThreadState*);
+		DECLARE_PYTHON_SYMBOL(int, PyGILState_Check, );
 		DECLARE_PYTHON_SYMBOL(void, _Py_NegativeRefcount, const char* COMMA int COMMA PyObject*);
 		DECLARE_PYTHON_SYMBOL(PyObject*, _PyObject_New, PyTypeObject*);
 #ifdef _DEBUG
@@ -197,8 +202,13 @@ namespace Plugins {
 					RESOLVE_PYTHON_SYMBOL(PyImport_ImportModule);
 					RESOLVE_PYTHON_SYMBOL(PyObject_CallObject);
 					RESOLVE_PYTHON_SYMBOL(PyFrame_GetLineNumber);
+					RESOLVE_PYTHON_SYMBOL(PyEval_InitThreads);
+					RESOLVE_PYTHON_SYMBOL(PyThreadState_Get);
 					RESOLVE_PYTHON_SYMBOL(PyEval_SaveThread);
 					RESOLVE_PYTHON_SYMBOL(PyEval_RestoreThread);
+					RESOLVE_PYTHON_SYMBOL(PyEval_ReleaseLock);
+					RESOLVE_PYTHON_SYMBOL(PyThreadState_Swap);
+					RESOLVE_PYTHON_SYMBOL(PyGILState_Check);
 					RESOLVE_PYTHON_SYMBOL(_Py_NegativeRefcount);
 					RESOLVE_PYTHON_SYMBOL(_PyObject_New);
 #ifdef _DEBUG
@@ -365,8 +375,13 @@ extern	SharedLibraryProxy* pythonLib;
 #define PyImport_ImportModule	pythonLib->PyImport_ImportModule
 #define PyObject_CallObject		pythonLib->PyObject_CallObject
 #define PyFrame_GetLineNumber	pythonLib->PyFrame_GetLineNumber
+#define	PyEval_InitThreads		pythonLib->PyEval_InitThreads
+#define	PyThreadState_Get		pythonLib->PyThreadState_Get
 #define PyEval_SaveThread		pythonLib->PyEval_SaveThread
 #define PyEval_RestoreThread	pythonLib->PyEval_RestoreThread
+#define PyEval_ReleaseLock		pythonLib->PyEval_ReleaseLock
+#define PyThreadState_Swap		pythonLib->PyThreadState_Swap
+#define PyGILState_Check		pythonLib->PyGILState_Check
 #define _Py_NegativeRefcount	pythonLib->_Py_NegativeRefcount
 #define _PyObject_New			pythonLib->_PyObject_New
 #define PyArg_ParseTuple		pythonLib->PyArg_ParseTuple
diff --git a/hardware/plugins/PluginManager.cpp b/hardware/plugins/PluginManager.cpp
index 72c04575f..e896b8bcc 100644
--- a/hardware/plugins/PluginManager.cpp
+++ b/hardware/plugins/PluginManager.cpp
@@ -141,6 +141,9 @@ namespace Plugins {
 			Py_Initialize();
 			m_InitialPythonThread = PyEval_SaveThread();
 
+			// Initialise threads. Python 3.7+ does this inside Py_Initialize so done here for compatibility
+			PyEval_InitThreads();
+
 			m_bEnabled = true;
 			_log.Log(LOG_STATUS, "PluginSystem: Started, Python version '%s'.", sVersion.c_str());
 		}
@@ -319,7 +322,13 @@ namespace Plugins {
 					}
 				}
 				// Free the memory for the message
-				if (Message) delete Message;
+				if (Message)
+				{
+					CPlugin* pPlugin = (CPlugin*)Message->Plugin();
+					pPlugin->RestoreThread();
+					delete Message;
+					pPlugin->ReleaseThread();
+				}
 			}
 			sleep_milliseconds(50);
 		}
diff --git a/hardware/plugins/PluginManager.h b/hardware/plugins/PluginManager.h
index 99e126e41..df66f63f6 100644
--- a/hardware/plugins/PluginManager.h
+++ b/hardware/plugins/PluginManager.h
@@ -36,10 +36,11 @@ namespace Plugins {
 		std::map<int, CDomoticzHardwareBase*>* GetHardware() { return &m_pPlugins; };
 		CDomoticzHardwareBase* RegisterPlugin(const int HwdID, const std::string &Name, const std::string &PluginKey);
 		void	 DeregisterPlugin(const int HwdID);
-		bool StopPluginSystem();
-		void AllPluginsStarted() { m_bAllPluginsStarted = true; };
+		bool	StopPluginSystem();
+		void	AllPluginsStarted() { m_bAllPluginsStarted = true; };
 		static void LoadSettings();
 		void	DeviceModified(uint64_t ID);
+		void*	PythonThread() { return m_InitialPythonThread; };
 	};
 };
 
diff --git a/hardware/plugins/PluginMessages.h b/hardware/plugins/PluginMessages.h
index f4e902553..b254c969e 100644
--- a/hardware/plugins/PluginMessages.h
+++ b/hardware/plugins/PluginMessages.h
@@ -73,6 +73,7 @@ namespace Plugins {
 			boost::lock_guard<boost::mutex> l(PythonMutex);
 			m_pPlugin->RestoreThread();
 			ProcessLocked();
+			m_pPlugin->ReleaseThread();
 		};
 		virtual const char* PythonName() { return m_Callback.c_str(); };
 	};
@@ -367,6 +368,7 @@ static std::string get_utf8_from_ansi(const std::string &utf8, int codepage)
 			boost::lock_guard<boost::mutex> l(PythonMutex);
 			m_pPlugin->RestoreThread();
 			ProcessLocked();
+			m_pPlugin->ReleaseThread();
 		};
 	};
 
@@ -454,6 +456,7 @@ static std::string get_utf8_from_ansi(const std::string &utf8, int codepage)
 			boost::lock_guard<boost::mutex> l(PythonMutex);
 			m_pPlugin->RestoreThread();
 			ProcessLocked();
+			m_pPlugin->ReleaseThread();
 		}
 	};
 
diff --git a/hardware/plugins/Plugins.cpp b/hardware/plugins/Plugins.cpp
index 329080b0a..9647a8c91 100644
--- a/hardware/plugins/Plugins.cpp
+++ b/hardware/plugins/Plugins.cpp
@@ -35,6 +35,7 @@ extern std::string szWWWFolder;
 extern std::string szAppVersion;
 extern std::string szAppHash;
 extern std::string szAppDate;
+extern MainWorker m_mainworker;
 
 namespace Plugins {
 
@@ -867,6 +868,7 @@ namespace Plugins {
 
 		try
 		{
+			PyEval_RestoreThread((PyThreadState*)m_mainworker.m_pluginsystem.PythonThread());
 			m_PyInterpreter = Py_NewInterpreter();
 			if (!m_PyInterpreter)
 			{
@@ -973,6 +975,7 @@ namespace Plugins {
 			}
 			_log.Log(LOG_STATUS, "(%s) Initialized %s", Name.c_str(), sExtraDetail.c_str());
 
+			PyEval_SaveThread();
 			return true;
 		}
 		catch (...)
@@ -981,6 +984,7 @@ namespace Plugins {
 		}
 
 Error:
+		PyEval_SaveThread();
 		m_bIsStarting = false;
 		return false;
 	}
@@ -1488,7 +1492,14 @@ namespace Plugins {
 
 	void CPlugin::RestoreThread()
 	{
-		if (m_PyInterpreter) PyEval_RestoreThread((PyThreadState*)m_PyInterpreter);
+		if (m_PyInterpreter)
+			PyEval_RestoreThread((PyThreadState*)m_PyInterpreter);
+	}
+
+	void CPlugin::ReleaseThread()
+	{
+		if (m_PyInterpreter)
+			PyEval_SaveThread();
 	}
 
 	void CPlugin::Callback(std::string sHandler, void * pParams)
@@ -1537,6 +1548,7 @@ namespace Plugins {
 			if (m_SettingsDict) Py_XDECREF(m_SettingsDict);
 			if (m_PyInterpreter) Py_EndInterpreter((PyThreadState*)m_PyInterpreter);
 			Py_XDECREF(m_PyModule);
+			PyEval_ReleaseLock();
 		}
 		catch (std::exception *e)
 		{
diff --git a/hardware/plugins/Plugins.h b/hardware/plugins/Plugins.h
index 0145747ee..d8808e147 100644
--- a/hardware/plugins/Plugins.h
+++ b/hardware/plugins/Plugins.h
@@ -78,6 +78,7 @@ namespace Plugins {
 		void	DisconnectEvent(CEventBase*);
 		void	Callback(std::string sHandler, void* pParams);
 		void	RestoreThread();
+		void	ReleaseThread();
 		void	Stop();
 
 		void	WriteDebugBuffer(const std::vector<byte>& Buffer, bool Incoming);
diff --git a/main/EventsPythonModule.cpp b/main/EventsPythonModule.cpp
index a839cb546..bdde645a9 100644
--- a/main/EventsPythonModule.cpp
+++ b/main/EventsPythonModule.cpp
@@ -122,6 +122,7 @@
             }
             
 			boost::lock_guard<boost::mutex> l(PythonMutex);
+			PyEval_RestoreThread((PyThreadState*)m_mainworker.m_pluginsystem.PythonThread());
 			m_PyInterpreter = Py_NewInterpreter();
             if (!m_PyInterpreter)
             {
@@ -144,7 +145,8 @@
             PythonEventsInitalized = 1;
             
             PyObject* pModule = Plugins::PythonEventsGetModule();
-            if (!pModule) {
+			PyEval_SaveThread();
+			if (!pModule) {
                 _log.Log(LOG_ERROR, "EventSystem - Python: Failed to initialize module.");
                 return false;
             }
@@ -159,7 +161,8 @@
 				if (Plugins::Py_IsInitialized())
 					Py_EndInterpreter((PyThreadState*)m_PyInterpreter);
 				m_PyInterpreter = NULL;
-                _log.Log(LOG_STATUS, "EventSystem - Python stopped...");
+				PyEval_ReleaseLock();
+				_log.Log(LOG_STATUS, "EventSystem - Python stopped...");
                 return true;
             } else
                 return false;
@@ -217,7 +220,8 @@
 
                    if (!pModuleDict) {
                        _log.Log(LOG_ERROR, "Python EventSystem: Failed to open module dictionary.");
-                       return;
+					   PyEval_SaveThread();
+					   return;
                    }
 
                    if (Plugins::PyDict_SetItemString(pModuleDict, "changed_device_name", Plugins::PyUnicode_FromString(m_devicestates[DeviceID].deviceName.c_str())) == -1) {
@@ -230,13 +234,15 @@
                    if (Plugins::PyDict_SetItemString(pModuleDict, "Devices", (PyObject*)m_DeviceDict) == -1)
                    {
                        _log.Log(LOG_ERROR, "Python EventSystem: Failed to add Device dictionary.");
-                       return;
+					   PyEval_SaveThread();
+					   return;
                    }
                    Py_DECREF(m_DeviceDict);
 
                    if (Plugins::PyType_Ready(&Plugins::PDeviceType) < 0) {
                        _log.Log(LOG_ERROR, "Python EventSystem: Unable to ready DeviceType Object.");
-                       return;
+					   PyEval_SaveThread();
+					   return;
                    }
 
                    // Mutex
@@ -332,7 +338,8 @@
                    if (Plugins::PyDict_SetItemString(pModuleDict, "user_variables", (PyObject*)m_uservariablesDict) == -1)
                    {
                        _log.Log(LOG_ERROR, "Python EventSystem: Failed to add uservariables dictionary.");
-                       return;
+					   PyEval_SaveThread();
+					   return;
                    }
                    Py_DECREF(m_uservariablesDict);
 
@@ -401,7 +408,10 @@
                 } else {
                     _log.Log(LOG_ERROR, "Python EventSystem: Module not available to events");
                 }
-            } else {
+
+				PyEval_SaveThread();
+
+			} else {
                 _log.Log(LOG_ERROR, "EventSystem: Python not initalized");
             }
 
From 64750ee9d6b9388d0d4c48f32679b2fb52bb9931 Mon Sep 17 00:00:00 2001
From: dnpwwo <kendel.boul@gmail.com>
Date: Sun, 1 Jul 2018 20:29:24 +1000
Subject: [PATCH] 1st level Python tracing added

---
 hardware/plugins/DelayedLink.h |   6 ++
 hardware/plugins/Plugins.cpp   | 133 +++++++++++++++++++++++++++++++++
 hardware/plugins/Plugins.h     |   1 +
 3 files changed, 140 insertions(+)

diff --git a/hardware/plugins/DelayedLink.h b/hardware/plugins/DelayedLink.h
index de9327a13..bdb9d0ec3 100644
--- a/hardware/plugins/DelayedLink.h
+++ b/hardware/plugins/DelayedLink.h
@@ -119,6 +119,8 @@ namespace Plugins {
 		DECLARE_PYTHON_SYMBOL(long, PyLong_AsLong, PyObject*);
 		DECLARE_PYTHON_SYMBOL(PyObject*, PyUnicode_AsUTF8String, PyObject*);
 		DECLARE_PYTHON_SYMBOL(PyObject*, PyImport_AddModule, const char*);
+		DECLARE_PYTHON_SYMBOL(void, PyEval_SetProfile, Py_tracefunc COMMA PyObject*);
+		DECLARE_PYTHON_SYMBOL(void, PyEval_SetTrace, Py_tracefunc COMMA PyObject*);
 
 #ifdef _DEBUG
 		// In a debug build dealloc is a function but for release builds its a macro
@@ -238,6 +240,8 @@ namespace Plugins {
 					RESOLVE_PYTHON_SYMBOL(PyLong_AsLong);
 					RESOLVE_PYTHON_SYMBOL(PyUnicode_AsUTF8String);
 					RESOLVE_PYTHON_SYMBOL(PyImport_AddModule);
+					RESOLVE_PYTHON_SYMBOL(PyEval_SetProfile);
+					RESOLVE_PYTHON_SYMBOL(PyEval_SetTrace);
 				}
 			}
 			_Py_NoneStruct.ob_refcnt = 1;
@@ -414,4 +418,6 @@ extern	SharedLibraryProxy* pythonLib;
 #define PyLong_AsLong			pythonLib->PyLong_AsLong
 #define PyUnicode_AsUTF8String	pythonLib->PyUnicode_AsUTF8String
 #define PyImport_AddModule		pythonLib->PyImport_AddModule
+#define PyEval_SetProfile		pythonLib->PyEval_SetProfile
+#define PyEval_SetTrace			pythonLib->PyEval_SetTrace
 }
diff --git a/hardware/plugins/Plugins.cpp b/hardware/plugins/Plugins.cpp
index 9647a8c91..05ef29b97 100644
--- a/hardware/plugins/Plugins.cpp
+++ b/hardware/plugins/Plugins.cpp
@@ -120,6 +120,96 @@ namespace Plugins {
 		if (pTraceback) Py_XDECREF(pTraceback);
 	}
 
+	int PyDomoticz_ProfileFunc(PyObject *self, PyFrameObject *frame, int what, PyObject *arg)
+	{
+		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
+		if (!pModState)
+		{
+			_log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
+		}
+		else if (!pModState->pPlugin)
+		{
+			_log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
+		}
+		else
+		{
+			int lineno = PyFrame_GetLineNumber(frame);
+			std::string	sFuncName = "Unknown";
+			PyCodeObject*	pCode = frame->f_code;
+			if (pCode && pCode->co_filename)
+			{
+				PyBytesObject*	pFileBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_filename);
+				sFuncName = pFileBytes->ob_sval;
+			}
+			if (pCode && pCode->co_name)
+			{
+				if (sFuncName.length()) sFuncName += "\\";
+				PyBytesObject*	pFuncBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_name);
+				sFuncName = pFuncBytes->ob_sval;
+			}
+
+			switch (what)
+			{
+			case PyTrace_CALL:
+				_log.Log(LOG_NORM, "(%s) Calling function at line %d in '%s'", pModState->pPlugin->Name.c_str(), lineno, sFuncName.c_str());
+				break;
+			case PyTrace_RETURN:
+				_log.Log(LOG_NORM, "(%s) Returning from line %d in '%s'", pModState->pPlugin->Name.c_str(), lineno, sFuncName.c_str());
+				break;
+			case PyTrace_EXCEPTION:
+				_log.Log(LOG_NORM, "(%s) Exception at line %d in '%s'", pModState->pPlugin->Name.c_str(), lineno, sFuncName.c_str());
+				break;
+			}
+		}
+
+		return 0;
+	}
+
+	int PyDomoticz_TraceFunc(PyObject *self, PyFrameObject *frame, int what, PyObject *arg)
+	{
+		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
+		if (!pModState)
+		{
+			_log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
+		}
+		else if (!pModState->pPlugin)
+		{
+			_log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
+		}
+		else
+		{
+			int lineno = PyFrame_GetLineNumber(frame);
+			std::string	sFuncName = "Unknown";
+			PyCodeObject*	pCode = frame->f_code;
+			if (pCode && pCode->co_filename)
+			{
+				PyBytesObject*	pFileBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_filename);
+				sFuncName = pFileBytes->ob_sval;
+			}
+			if (pCode && pCode->co_name)
+			{
+				if (sFuncName.length()) sFuncName += "\\";
+				PyBytesObject*	pFuncBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_name);
+				sFuncName = pFuncBytes->ob_sval;
+			}
+
+			switch (what)
+			{
+			case PyTrace_CALL:
+				_log.Log(LOG_NORM, "(%s) Calling function at line %d in '%s'", pModState->pPlugin->Name.c_str(), lineno, sFuncName.c_str());
+				break;
+			case PyTrace_LINE:
+				_log.Log(LOG_NORM, "(%s) Executing line %d in '%s'", pModState->pPlugin->Name.c_str(), lineno, sFuncName.c_str());
+				break;
+			case PyTrace_EXCEPTION:
+				_log.Log(LOG_NORM, "(%s) Exception at line %d in '%s'", pModState->pPlugin->Name.c_str(), lineno, sFuncName.c_str());
+				break;
+			}
+		}
+
+		return 0;
+	}
+
 	static PyObject*	PyDomoticz_Debug(PyObject *self, PyObject *args)
 	{
 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
@@ -355,6 +445,47 @@ namespace Plugins {
 		return Py_None;
 	}
 
+	static PyObject*	PyDomoticz_Trace(PyObject *self, PyObject *args)
+	{
+		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
+		if (!pModState)
+		{
+			_log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
+		}
+		else if (!pModState->pPlugin)
+		{
+			_log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
+		}
+		else
+		{
+			int		bTrace = 0;
+			if (!PyArg_ParseTuple(args, "p", &bTrace))
+			{
+				_log.Log(LOG_ERROR, "(%s) failed to parse parameter, True/False expected.", pModState->pPlugin->Name.c_str());
+				LogPythonException(pModState->pPlugin, std::string(__func__));
+			}
+			else
+			{
+				pModState->pPlugin->m_bTracing = (bool)bTrace;
+				_log.Log(LOG_NORM, "(%s) Low level Python tracing %s.", pModState->pPlugin->Name.c_str(), (pModState->pPlugin->m_bTracing ? "ENABLED" : "DISABLED"));
+
+				if (pModState->pPlugin->m_bTracing)
+				{
+					PyEval_SetProfile(PyDomoticz_ProfileFunc, self);
+					PyEval_SetTrace(PyDomoticz_TraceFunc, self);
+				}
+				else
+				{
+					PyEval_SetProfile(NULL, NULL);
+					PyEval_SetTrace(NULL, NULL);
+				}
+			}
+		}
+
+		Py_INCREF(Py_None);
+		return Py_None;
+	}
+
 	static PyMethodDef DomoticzMethods[] = {
 		{ "Debug", PyDomoticz_Debug, METH_VARARGS, "Write a message to Domoticz log only if verbose logging is turned on." },
 		{ "Log", PyDomoticz_Log, METH_VARARGS, "Write a message to Domoticz log." },
@@ -363,6 +494,7 @@ namespace Plugins {
 		{ "Debugging", PyDomoticz_Debugging, METH_VARARGS, "Set logging level. 1 set verbose logging, all other values use default level" },
 		{ "Heartbeat", PyDomoticz_Heartbeat, METH_VARARGS, "Set the heartbeat interval, default 10 seconds." },
 		{ "Notifier", PyDomoticz_Notifier, METH_VARARGS, "Enable notification handling with supplied name." },
+		{ "Trace", PyDomoticz_Trace, METH_VARARGS, "Enable/Disable line level Python tracing." },
 		{ NULL, NULL, 0, NULL }
 	};
 
@@ -439,6 +571,7 @@ namespace Plugins {
 		Name = sName;
 		m_bIsStarted = false;
 		m_bIsStarting = false;
+		m_bTracing = false;
 	}
 
 	CPlugin::~CPlugin(void)
diff --git a/hardware/plugins/Plugins.h b/hardware/plugins/Plugins.h
index d8808e147..d184e2be3 100644
--- a/hardware/plugins/Plugins.h
+++ b/hardware/plugins/Plugins.h
@@ -108,6 +108,7 @@ namespace Plugins {
 		PluginDebugMask		m_bDebug;
 		bool				m_stoprequested;
 		bool				m_bIsStarting;
+		bool				m_bTracing;
 	};
 
 	class CPluginNotifier : public CNotificationBase

From 463bb8e28be5ec7a6cfc452a5318f89f2d65a14d Mon Sep 17 00:00:00 2001
From: dnpwwo <kendel.boul@gmail.com>
Date: Sun, 1 Jul 2018 22:56:32 +1000
Subject: [PATCH] 2nd version, Python 3.7 and 3.6

---
 hardware/plugins/DelayedLink.h     | 3 +++
 hardware/plugins/PluginManager.cpp | 8 ++++++--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/hardware/plugins/DelayedLink.h b/hardware/plugins/DelayedLink.h
index bdb9d0ec3..44bcb118e 100644
--- a/hardware/plugins/DelayedLink.h
+++ b/hardware/plugins/DelayedLink.h
@@ -87,6 +87,7 @@ namespace Plugins {
 		DECLARE_PYTHON_SYMBOL(PyObject*, PyObject_CallObject, PyObject* COMMA PyObject*);
 		DECLARE_PYTHON_SYMBOL(int, PyFrame_GetLineNumber, PyFrameObject*);
 		DECLARE_PYTHON_SYMBOL(void, PyEval_InitThreads, );
+		DECLARE_PYTHON_SYMBOL(int, PyEval_ThreadsInitialized, );
 		DECLARE_PYTHON_SYMBOL(PyThreadState*, PyThreadState_Get, );
 		DECLARE_PYTHON_SYMBOL(PyThreadState*, PyEval_SaveThread, void);
 		DECLARE_PYTHON_SYMBOL(void, PyEval_RestoreThread, PyThreadState*);
@@ -205,6 +206,7 @@ namespace Plugins {
 					RESOLVE_PYTHON_SYMBOL(PyObject_CallObject);
 					RESOLVE_PYTHON_SYMBOL(PyFrame_GetLineNumber);
 					RESOLVE_PYTHON_SYMBOL(PyEval_InitThreads);
+					RESOLVE_PYTHON_SYMBOL(PyEval_ThreadsInitialized);
 					RESOLVE_PYTHON_SYMBOL(PyThreadState_Get);
 					RESOLVE_PYTHON_SYMBOL(PyEval_SaveThread);
 					RESOLVE_PYTHON_SYMBOL(PyEval_RestoreThread);
@@ -380,6 +382,7 @@ extern	SharedLibraryProxy* pythonLib;
 #define PyObject_CallObject		pythonLib->PyObject_CallObject
 #define PyFrame_GetLineNumber	pythonLib->PyFrame_GetLineNumber
 #define	PyEval_InitThreads		pythonLib->PyEval_InitThreads
+#define	PyEval_ThreadsInitialized	pythonLib->PyEval_ThreadsInitialized
 #define	PyThreadState_Get		pythonLib->PyThreadState_Get
 #define PyEval_SaveThread		pythonLib->PyEval_SaveThread
 #define PyEval_RestoreThread	pythonLib->PyEval_RestoreThread
diff --git a/hardware/plugins/PluginManager.cpp b/hardware/plugins/PluginManager.cpp
index e896b8bcc..32fa2bffd 100644
--- a/hardware/plugins/PluginManager.cpp
+++ b/hardware/plugins/PluginManager.cpp
@@ -139,10 +139,14 @@ namespace Plugins {
 			}
 
 			Py_Initialize();
-			m_InitialPythonThread = PyEval_SaveThread();
 
 			// Initialise threads. Python 3.7+ does this inside Py_Initialize so done here for compatibility
-			PyEval_InitThreads();
+			if (!PyEval_ThreadsInitialized())
+			{
+				PyEval_InitThreads();
+			}
+
+			m_InitialPythonThread = PyEval_SaveThread();
 
 			m_bEnabled = true;
 			_log.Log(LOG_STATUS, "PluginSystem: Started, Python version '%s'.", sVersion.c_str());