aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Carroll <jim@dontcallme.com>2019-10-20 15:51:20 -0400
committerJim Carroll <jim@dontcallme.com>2019-10-20 15:51:20 -0400
commit312ee95425f3d47215093e3fd0c67de1f42c8c28 (patch)
tree1a8a0aa857b01dfafb0b6cd0464d41e0d079129f
parent8679e4d5e298b3a7bae575c0fbb69bd8e405694f (diff)
Revert "Revert "Reusepython""
-rw-r--r--addons/xbmc.addon/metadata.xsd1
-rw-r--r--xbmc/addons/addoninfo/AddonInfoBuilder.cpp5
-rw-r--r--xbmc/filesystem/PluginDirectory.cpp19
-rw-r--r--xbmc/filesystem/PluginDirectory.h2
-rw-r--r--xbmc/interfaces/generic/ILanguageInvocationHandler.h2
-rw-r--r--xbmc/interfaces/generic/ILanguageInvoker.cpp6
-rw-r--r--xbmc/interfaces/generic/ILanguageInvoker.h4
-rw-r--r--xbmc/interfaces/generic/LanguageInvokerThread.cpp49
-rw-r--r--xbmc/interfaces/generic/LanguageInvokerThread.h15
-rw-r--r--xbmc/interfaces/generic/ScriptInvocationManager.cpp101
-rw-r--r--xbmc/interfaces/generic/ScriptInvocationManager.h38
-rw-r--r--xbmc/interfaces/python/PythonInvoker.cpp384
-rw-r--r--xbmc/interfaces/python/PythonInvoker.h9
-rw-r--r--xbmc/interfaces/python/XBPython.cpp6
-rw-r--r--xbmc/interfaces/python/XBPython.h2
15 files changed, 424 insertions, 219 deletions
diff --git a/addons/xbmc.addon/metadata.xsd b/addons/xbmc.addon/metadata.xsd
index 03fbf241f5..89058e9e47 100644
--- a/addons/xbmc.addon/metadata.xsd
+++ b/addons/xbmc.addon/metadata.xsd
@@ -16,6 +16,7 @@
<xs:element name="email" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="broken" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="news" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="reuselanguageinvoker" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="assets" type="assetsList" minOccurs="0" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="point" type="xs:string" use="required"/>
diff --git a/xbmc/addons/addoninfo/AddonInfoBuilder.cpp b/xbmc/addons/addoninfo/AddonInfoBuilder.cpp
index ef80cb513d..233789587d 100644
--- a/xbmc/addons/addoninfo/AddonInfoBuilder.cpp
+++ b/xbmc/addons/addoninfo/AddonInfoBuilder.cpp
@@ -318,6 +318,11 @@ bool CAddonInfoBuilder::ParseXML(const AddonInfoPtr& addon, const TiXmlElement*
if (element && element->GetText() != nullptr)
addon->AddExtraInfo("language", element->GetText());
+ /* Parse addon.xml "<reuselanguageinvoker">...</reuselanguageinvoker>" */
+ element = child->FirstChildElement("reuselanguageinvoker");
+ if (element && element->GetText() != nullptr)
+ addon->AddExtraInfo("reuselanguageinvoker", element->GetText());
+
/* Parse addon.xml "<noicon">...</noicon>" */
if (addon->m_icon.empty())
{
diff --git a/xbmc/filesystem/PluginDirectory.cpp b/xbmc/filesystem/PluginDirectory.cpp
index aaa4b94d30..6766f86bbc 100644
--- a/xbmc/filesystem/PluginDirectory.cpp
+++ b/xbmc/filesystem/PluginDirectory.cpp
@@ -86,6 +86,12 @@ int CPluginDirectory::getNewHandle(CPluginDirectory *cp)
return handle;
}
+void CPluginDirectory::reuseHandle(int handle, CPluginDirectory *cp)
+{
+ CSingleLock lock(m_handleLock);
+ globalHandles[handle] = cp;
+}
+
void CPluginDirectory::removeHandle(int handle)
{
CSingleLock lock(m_handleLock);
@@ -124,7 +130,12 @@ bool CPluginDirectory::StartScript(const std::string& strPath, bool retrievingDi
std::string basePath(url.Get());
// reset our wait event, and grab a new handle
m_fetchComplete.Reset();
- int handle = getNewHandle(this);
+ int handle = CScriptInvocationManager::GetInstance().GetReusablePluginHandle(m_addon->LibPath());
+
+ if (handle < 0)
+ handle = getNewHandle(this);
+ else
+ reuseHandle(handle, this);
// clear out our status variables
m_fileResult->Reset();
@@ -151,7 +162,11 @@ bool CPluginDirectory::StartScript(const std::string& strPath, bool retrievingDi
CLog::Log(LOGDEBUG, "%s - calling plugin %s('%s','%s','%s','%s')", __FUNCTION__, m_addon->Name().c_str(), argv[0].c_str(), argv[1].c_str(), argv[2].c_str(), argv[3].c_str());
bool success = false;
std::string file = m_addon->LibPath();
- int id = CScriptInvocationManager::GetInstance().ExecuteAsync(file, m_addon, argv);
+ bool reuseLanguageInvoker = false;
+ if (m_addon->ExtraInfo().find("reuselanguageinvoker") != m_addon->ExtraInfo().end())
+ reuseLanguageInvoker = m_addon->ExtraInfo().at("reuselanguageinvoker") == "true";
+
+ int id = CScriptInvocationManager::GetInstance().ExecuteAsync(file, m_addon, argv, reuseLanguageInvoker, handle);
if (id >= 0)
{ // wait for our script to finish
std::string scriptName = m_addon->Name();
diff --git a/xbmc/filesystem/PluginDirectory.h b/xbmc/filesystem/PluginDirectory.h
index 3eb8bfaf48..039c29d52a 100644
--- a/xbmc/filesystem/PluginDirectory.h
+++ b/xbmc/filesystem/PluginDirectory.h
@@ -75,6 +75,8 @@ private:
static std::map<int,CPluginDirectory*> globalHandles;
static int getNewHandle(CPluginDirectory *cp);
+ static void reuseHandle(int handle, CPluginDirectory *cp);
+
static void removeHandle(int handle);
static CPluginDirectory *dirFromHandle(int handle);
static CCriticalSection m_handleLock;
diff --git a/xbmc/interfaces/generic/ILanguageInvocationHandler.h b/xbmc/interfaces/generic/ILanguageInvocationHandler.h
index 3057132cfb..e3912355ca 100644
--- a/xbmc/interfaces/generic/ILanguageInvocationHandler.h
+++ b/xbmc/interfaces/generic/ILanguageInvocationHandler.h
@@ -24,7 +24,7 @@ public:
virtual bool OnScriptInitialized(ILanguageInvoker *invoker) { return true; }
virtual void OnScriptStarted(ILanguageInvoker *invoker) { }
virtual void OnScriptAbortRequested(ILanguageInvoker *invoker) { }
- virtual void OnScriptEnded(ILanguageInvoker *invoker) { }
+ virtual void OnExecutionEnded(ILanguageInvoker *invoker) { }
virtual void OnScriptFinalized(ILanguageInvoker *invoker) { }
virtual ILanguageInvoker* CreateInvoker() = 0;
diff --git a/xbmc/interfaces/generic/ILanguageInvoker.cpp b/xbmc/interfaces/generic/ILanguageInvoker.cpp
index 0578c75978..9918c583ac 100644
--- a/xbmc/interfaces/generic/ILanguageInvoker.cpp
+++ b/xbmc/interfaces/generic/ILanguageInvoker.cpp
@@ -36,7 +36,7 @@ bool ILanguageInvoker::Stop(bool abort /* = false */)
bool ILanguageInvoker::IsActive() const
{
- return GetState() > InvokerStateUninitialized && GetState() < InvokerStateDone;
+ return GetState() > InvokerStateUninitialized && GetState() < InvokerStateScriptDone;
}
bool ILanguageInvoker::IsRunning() const
@@ -72,13 +72,13 @@ void ILanguageInvoker::onAbortRequested()
void ILanguageInvoker::onExecutionFailed()
{
if (m_invocationHandler)
- m_invocationHandler->OnScriptEnded(this);
+ m_invocationHandler->OnExecutionEnded(this);
}
void ILanguageInvoker::onExecutionDone()
{
if (m_invocationHandler)
- m_invocationHandler->OnScriptEnded(this);
+ m_invocationHandler->OnExecutionEnded(this);
}
void ILanguageInvoker::onExecutionFinalized()
diff --git a/xbmc/interfaces/generic/ILanguageInvoker.h b/xbmc/interfaces/generic/ILanguageInvoker.h
index 3a8f6819fc..29cc422793 100644
--- a/xbmc/interfaces/generic/ILanguageInvoker.h
+++ b/xbmc/interfaces/generic/ILanguageInvoker.h
@@ -22,7 +22,8 @@ typedef enum {
InvokerStateInitialized,
InvokerStateRunning,
InvokerStateStopping,
- InvokerStateDone,
+ InvokerStateScriptDone,
+ InvokerStateExecutionDone,
InvokerStateFailed
} InvokerState;
@@ -43,6 +44,7 @@ public:
InvokerState GetState() const { return m_state; }
bool IsActive() const;
bool IsRunning() const;
+ void Reset() { m_state = InvokerStateUninitialized; };
protected:
friend class CLanguageInvokerThread;
diff --git a/xbmc/interfaces/generic/LanguageInvokerThread.cpp b/xbmc/interfaces/generic/LanguageInvokerThread.cpp
index 81c923ec61..87359b00c4 100644
--- a/xbmc/interfaces/generic/LanguageInvokerThread.cpp
+++ b/xbmc/interfaces/generic/LanguageInvokerThread.cpp
@@ -10,11 +10,12 @@
#include "ScriptInvocationManager.h"
-CLanguageInvokerThread::CLanguageInvokerThread(LanguageInvokerPtr invoker, CScriptInvocationManager* invocationManager)
+CLanguageInvokerThread::CLanguageInvokerThread(LanguageInvokerPtr invoker, CScriptInvocationManager *invocationManager, bool reuseable)
: ILanguageInvoker(NULL),
CThread("LanguageInvoker"),
m_invoker(invoker),
- m_invocationManager(invocationManager)
+ m_invocationManager(invocationManager),
+ m_reusable(reuseable)
{ }
CLanguageInvokerThread::~CLanguageInvokerThread()
@@ -22,7 +23,7 @@ CLanguageInvokerThread::~CLanguageInvokerThread()
Stop(true);
}
-InvokerState CLanguageInvokerThread::GetState()
+InvokerState CLanguageInvokerThread::GetState() const
{
if (m_invoker == NULL)
return InvokerStateFailed;
@@ -30,6 +31,12 @@ InvokerState CLanguageInvokerThread::GetState()
return m_invoker->GetState();
}
+void CLanguageInvokerThread::Release()
+{
+ m_bStop = true;
+ m_condition.notify_one();
+}
+
bool CLanguageInvokerThread::execute(const std::string &script, const std::vector<std::string> &arguments)
{
if (m_invoker == NULL || script.empty())
@@ -38,7 +45,17 @@ bool CLanguageInvokerThread::execute(const std::string &script, const std::vecto
m_script = script;
m_args = arguments;
- Create();
+ if (CThread::IsRunning())
+ {
+ std::unique_lock<std::mutex> lck(m_mutex);
+ m_restart = true;
+ m_condition.notify_one();
+ }
+ else
+ Create();
+
+ //Todo wait until running
+
return true;
}
@@ -50,14 +67,16 @@ bool CLanguageInvokerThread::stop(bool wait)
if (!CThread::IsRunning())
return false;
+ Release();
+
bool result = true;
- if (m_invoker->GetState() < InvokerStateDone)
+ if (m_invoker->GetState() < InvokerStateExecutionDone)
{
// stop the language-specific invoker
result = m_invoker->Stop(wait);
- // stop the thread
- CThread::StopThread(wait);
}
+ // stop the thread
+ CThread::StopThread(wait);
return result;
}
@@ -77,7 +96,17 @@ void CLanguageInvokerThread::Process()
if (m_invoker == NULL)
return;
- m_invoker->Execute(m_script, m_args);
+ std::unique_lock<std::mutex> lckdl(m_mutex);
+ do {
+ m_restart = false;
+ m_invoker->Execute(m_script, m_args);
+
+ if (m_invoker->GetState() != InvokerStateScriptDone)
+ m_reusable = false;
+
+ m_condition.wait(lckdl, [this] {return m_bStop || m_restart || !m_reusable; });
+
+ } while (m_reusable && !m_bStop);
}
void CLanguageInvokerThread::OnExit()
@@ -86,7 +115,7 @@ void CLanguageInvokerThread::OnExit()
return;
m_invoker->onExecutionDone();
- m_invocationManager->OnScriptEnded(GetId());
+ m_invocationManager->OnExecutionDone(GetId());
}
void CLanguageInvokerThread::OnException()
@@ -95,5 +124,5 @@ void CLanguageInvokerThread::OnException()
return;
m_invoker->onExecutionFailed();
- m_invocationManager->OnScriptEnded(GetId());
+ m_invocationManager->OnExecutionDone(GetId());
} \ No newline at end of file
diff --git a/xbmc/interfaces/generic/LanguageInvokerThread.h b/xbmc/interfaces/generic/LanguageInvokerThread.h
index 5bd1b60098..81e4915ff5 100644
--- a/xbmc/interfaces/generic/LanguageInvokerThread.h
+++ b/xbmc/interfaces/generic/LanguageInvokerThread.h
@@ -19,10 +19,15 @@ class CScriptInvocationManager;
class CLanguageInvokerThread : public ILanguageInvoker, protected CThread
{
public:
- CLanguageInvokerThread(LanguageInvokerPtr invoker, CScriptInvocationManager* invocationManager);
+ CLanguageInvokerThread(LanguageInvokerPtr invoker, CScriptInvocationManager *invocationManager, bool reuseable);
~CLanguageInvokerThread() override;
- virtual InvokerState GetState();
+ virtual InvokerState GetState() const;
+
+ const std::string &GetScript() const { return m_script; };
+ LanguageInvokerPtr GetInvoker() const { return m_invoker; };
+ bool Reuseable(const std::string &script) const { return !m_bStop && m_reusable && GetState() == InvokerStateScriptDone && m_script == script; };
+ virtual void Release();
protected:
bool execute(const std::string &script, const std::vector<std::string> &arguments) override;
@@ -38,4 +43,10 @@ private:
CScriptInvocationManager *m_invocationManager;
std::string m_script;
std::vector<std::string> m_args;
+
+ std::mutex m_mutex;
+ std::condition_variable m_condition;
+ bool m_restart = false;
+ bool m_reusable = false;
};
+
diff --git a/xbmc/interfaces/generic/ScriptInvocationManager.cpp b/xbmc/interfaces/generic/ScriptInvocationManager.cpp
index 22945b682f..373aaeb911 100644
--- a/xbmc/interfaces/generic/ScriptInvocationManager.cpp
+++ b/xbmc/interfaces/generic/ScriptInvocationManager.cpp
@@ -77,6 +77,9 @@ void CScriptInvocationManager::Uninitialize()
// execute Process() once more to handle the remaining scripts
Process();
+ // it is safe to relese early, thread must be in m_scripts too
+ m_lastInvokerThread = nullptr;
+
// make sure all scripts are done
std::vector<LanguageInvokerThread> tempList;
for (const auto& script : m_scripts)
@@ -96,9 +99,11 @@ void CScriptInvocationManager::Uninitialize()
if (!it.done)
it.thread->Stop(true);
}
- tempList.clear();
lock.Enter();
+
+ tempList.clear();
+
// uninitialize all invocation handlers and then remove them
for (auto& it : m_invocationHandlers)
it.second->Uninitialize();
@@ -175,12 +180,39 @@ bool CScriptInvocationManager::HasLanguageInvoker(const std::string &script) con
return it != m_invocationHandlers.end() && it->second != NULL;
}
-LanguageInvokerPtr CScriptInvocationManager::GetLanguageInvoker(const std::string& script) const
+int CScriptInvocationManager::GetReusablePluginHandle(const std::string &script)
{
+ CSingleLock lock(m_critSection);
+
+ if (m_lastInvokerThread)
+ {
+ if (m_lastInvokerThread->Reuseable(script))
+ return m_lastPluginHandle;
+ m_lastInvokerThread->Release();
+ m_lastInvokerThread = nullptr;
+ }
+ return -1;
+}
+
+LanguageInvokerPtr CScriptInvocationManager::GetLanguageInvoker(const std::string &script)
+{
+ CSingleLock lock(m_critSection);
+
+ if (m_lastInvokerThread)
+ {
+ if (m_lastInvokerThread->Reuseable(script))
+ {
+ CLog::Log(LOGDEBUG, "%s - Reusing LanguageInvokerThread %d for script %s", __FUNCTION__, m_lastInvokerThread->GetId(), script.c_str());
+ m_lastInvokerThread->GetInvoker()->Reset();
+ return m_lastInvokerThread->GetInvoker();
+ }
+ m_lastInvokerThread->Release();
+ m_lastInvokerThread = nullptr;
+ }
+
std::string extension = URIUtils::GetExtension(script);
StringUtils::ToLower(extension);
- CSingleLock lock(m_critSection);
std::map<std::string, ILanguageInvocationHandler*>::const_iterator it = m_invocationHandlers.find(extension);
if (it != m_invocationHandlers.end() && it->second != NULL)
return LanguageInvokerPtr(it->second->CreateInvoker());
@@ -188,7 +220,11 @@ LanguageInvokerPtr CScriptInvocationManager::GetLanguageInvoker(const std::strin
return LanguageInvokerPtr();
}
-int CScriptInvocationManager::ExecuteAsync(const std::string &script, const ADDON::AddonPtr &addon /* = ADDON::AddonPtr() */, const std::vector<std::string> &arguments /* = std::vector<std::string>() */)
+int CScriptInvocationManager::ExecuteAsync(const std::string &script,
+ const ADDON::AddonPtr &addon /* = ADDON::AddonPtr() */,
+ const std::vector<std::string> &arguments /* = std::vector<std::string>() */,
+ bool reuseable /* = false */,
+ int pluginHandle /* = -1 */)
{
if (script.empty())
return -1;
@@ -200,10 +236,15 @@ int CScriptInvocationManager::ExecuteAsync(const std::string &script, const ADDO
}
LanguageInvokerPtr invoker = GetLanguageInvoker(script);
- return ExecuteAsync(script, invoker, addon, arguments);
+ return ExecuteAsync(script, invoker, addon, arguments, reuseable, pluginHandle);
}
-int CScriptInvocationManager::ExecuteAsync(const std::string &script, LanguageInvokerPtr languageInvoker, const ADDON::AddonPtr &addon /* = ADDON::AddonPtr() */, const std::vector<std::string> &arguments /* = std::vector<std::string>() */)
+int CScriptInvocationManager::ExecuteAsync(const std::string &script,
+ LanguageInvokerPtr languageInvoker,
+ const ADDON::AddonPtr &addon /* = ADDON::AddonPtr() */,
+ const std::vector<std::string> &arguments /* = std::vector<std::string>() */,
+ bool reuseable /* = false */,
+ int pluginHandle /* = -1 */)
{
if (script.empty() || languageInvoker == NULL)
return -1;
@@ -214,26 +255,47 @@ int CScriptInvocationManager::ExecuteAsync(const std::string &script, LanguageIn
return -1;
}
- CLanguageInvokerThreadPtr invokerThread = CLanguageInvokerThreadPtr(new CLanguageInvokerThread(languageInvoker, this));
- if (invokerThread == NULL)
+ CSingleLock lock(m_critSection);
+
+ if (m_lastInvokerThread && m_lastInvokerThread->GetInvoker() == languageInvoker)
+ {
+ if (addon != NULL)
+ m_lastInvokerThread->SetAddon(addon);
+
+ // After we leave the lock, m_lastInvokerThread can be released -> copy!
+ CLanguageInvokerThreadPtr invokerThread = m_lastInvokerThread;
+ lock.Leave();
+ invokerThread->Execute(script, arguments);
+
+ return invokerThread->GetId();
+ }
+
+ m_lastInvokerThread = CLanguageInvokerThreadPtr(new CLanguageInvokerThread(languageInvoker, this, reuseable));
+ if (m_lastInvokerThread == NULL)
return -1;
if (addon != NULL)
- invokerThread->SetAddon(addon);
+ m_lastInvokerThread->SetAddon(addon);
- CSingleLock lock(m_critSection);
- invokerThread->SetId(m_nextId++);
+ m_lastInvokerThread->SetId(m_nextId++);
+ m_lastPluginHandle = pluginHandle;
- LanguageInvokerThread thread = {invokerThread, script, false};
- m_scripts.insert(std::make_pair(invokerThread->GetId(), thread));
- m_scriptPaths.insert(std::make_pair(script, invokerThread->GetId()));
+ LanguageInvokerThread thread = { m_lastInvokerThread, script, false };
+ m_scripts.insert(std::make_pair(m_lastInvokerThread->GetId(), thread));
+ m_scriptPaths.insert(std::make_pair(script, m_lastInvokerThread->GetId()));
+ // After we leave the lock, m_lastInvokerThread can be released -> copy!
+ CLanguageInvokerThreadPtr invokerThread = m_lastInvokerThread;
lock.Leave();
invokerThread->Execute(script, arguments);
return invokerThread->GetId();
}
-int CScriptInvocationManager::ExecuteSync(const std::string &script, const ADDON::AddonPtr &addon /* = ADDON::AddonPtr() */, const std::vector<std::string> &arguments /* = std::vector<std::string>() */, uint32_t timeoutMs /* = 0 */, bool waitShutdown /* = false */)
+int CScriptInvocationManager::ExecuteSync(const std::string &script,
+ const ADDON::AddonPtr &addon /* = ADDON::AddonPtr() */,
+ const std::vector<std::string> &arguments /* = std::vector<std::string>() */,
+ uint32_t timeoutMs /* = 0 */,
+ bool waitShutdown /* = false */)
{
if (script.empty())
return -1;
@@ -248,7 +310,12 @@ int CScriptInvocationManager::ExecuteSync(const std::string &script, const ADDON
return ExecuteSync(script, invoker, addon, arguments, timeoutMs, waitShutdown);
}
-int CScriptInvocationManager::ExecuteSync(const std::string &script, LanguageInvokerPtr languageInvoker, const ADDON::AddonPtr &addon /* = ADDON::AddonPtr() */, const std::vector<std::string> &arguments /* = std::vector<std::string>() */, uint32_t timeoutMs /* = 0 */, bool waitShutdown /* = false */)
+int CScriptInvocationManager::ExecuteSync(const std::string &script,
+ LanguageInvokerPtr languageInvoker,
+ const ADDON::AddonPtr &addon /* = ADDON::AddonPtr() */,
+ const std::vector<std::string> &arguments /* = std::vector<std::string>() */,
+ uint32_t timeoutMs /* = 0 */,
+ bool waitShutdown /* = false */)
{
int scriptId = ExecuteAsync(script, languageInvoker, addon, arguments);
if (scriptId < 0)
@@ -322,7 +389,7 @@ bool CScriptInvocationManager::IsRunning(const std::string& scriptPath) const
return IsRunning(it->second);
}
-void CScriptInvocationManager::OnScriptEnded(int scriptId)
+void CScriptInvocationManager::OnExecutionDone(int scriptId)
{
if (scriptId < 0)
return;
diff --git a/xbmc/interfaces/generic/ScriptInvocationManager.h b/xbmc/interfaces/generic/ScriptInvocationManager.h
index ac1fec79ec..cf87d1383f 100644
--- a/xbmc/interfaces/generic/ScriptInvocationManager.h
+++ b/xbmc/interfaces/generic/ScriptInvocationManager.h
@@ -32,7 +32,12 @@ public:
void RegisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler, const std::set<std::string> &extensions);
void UnregisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler);
bool HasLanguageInvoker(const std::string &script) const;
- LanguageInvokerPtr GetLanguageInvoker(const std::string& script) const;
+ LanguageInvokerPtr GetLanguageInvoker(const std::string &script);
+
+ /*!
+ * \brief Returns addon_handle if last reusable invoker is ready to use.
+ */
+ int GetReusablePluginHandle(const std::string &script);
/*!
* \brief Executes the given script asynchronously in a separate thread.
@@ -42,7 +47,11 @@ public:
* \param arguments (Optional) List of arguments passed to the script
* \return -1 if an error occurred, otherwise the ID of the script
*/
- int ExecuteAsync(const std::string &script, const ADDON::AddonPtr &addon = ADDON::AddonPtr(), const std::vector<std::string> &arguments = std::vector<std::string>());
+ int ExecuteAsync(const std::string &script,
+ const ADDON::AddonPtr &addon = ADDON::AddonPtr(),
+ const std::vector<std::string> &arguments = std::vector<std::string>(),
+ bool reuseable = false,
+ int pluginHandle = -1);
/*!
* \brief Executes the given script asynchronously in a separate thread.
*
@@ -52,7 +61,12 @@ public:
* \param arguments (Optional) List of arguments passed to the script
* \return -1 if an error occurred, otherwise the ID of the script
*/
- int ExecuteAsync(const std::string &script, LanguageInvokerPtr languageInvoker, const ADDON::AddonPtr &addon = ADDON::AddonPtr(), const std::vector<std::string> &arguments = std::vector<std::string>());
+ int ExecuteAsync(const std::string &script,
+ LanguageInvokerPtr languageInvoker,
+ const ADDON::AddonPtr &addon = ADDON::AddonPtr(),
+ const std::vector<std::string> &arguments = std::vector<std::string>(),
+ bool reuseable = false,
+ int pluginHandle = -1);
/*!
* \brief Executes the given script synchronously.
@@ -70,7 +84,11 @@ public:
* \param waitShutdown (Optional) Whether to wait when having to forcefully stop the script's execution or not.
* \return -1 if an error occurred, 0 if the script terminated or ETIMEDOUT if the given timeout expired
*/
- int ExecuteSync(const std::string &script, const ADDON::AddonPtr &addon = ADDON::AddonPtr(), const std::vector<std::string> &arguments = std::vector<std::string>(), uint32_t timeoutMs = 0, bool waitShutdown = false);
+ int ExecuteSync(const std::string &script,
+ const ADDON::AddonPtr &addon = ADDON::AddonPtr(),
+ const std::vector<std::string> &arguments = std::vector<std::string>(),
+ uint32_t timeoutMs = 0,
+ bool waitShutdown = false);
/*!
* \brief Executes the given script synchronously.
*
@@ -88,7 +106,12 @@ public:
* \param waitShutdown (Optional) Whether to wait when having to forcefully stop the script's execution or not.
* \return -1 if an error occurred, 0 if the script terminated or ETIMEDOUT if the given timeout expired
*/
- int ExecuteSync(const std::string &script, LanguageInvokerPtr languageInvoker, const ADDON::AddonPtr &addon = ADDON::AddonPtr(), const std::vector<std::string> &arguments = std::vector<std::string>(), uint32_t timeoutMs = 0, bool waitShutdown = false);
+ int ExecuteSync(const std::string &script,
+ LanguageInvokerPtr languageInvoker,
+ const ADDON::AddonPtr &addon = ADDON::AddonPtr(),
+ const std::vector<std::string> &arguments = std::vector<std::string>(),
+ uint32_t timeoutMs = 0,
+ bool waitShutdown = false);
bool Stop(int scriptId, bool wait = false);
bool Stop(const std::string &scriptPath, bool wait = false);
@@ -98,7 +121,7 @@ public:
protected:
friend class CLanguageInvokerThread;
- void OnScriptEnded(int scriptId);
+ void OnExecutionDone(int scriptId);
private:
CScriptInvocationManager() = default;
@@ -118,6 +141,9 @@ private:
LanguageInvocationHandlerMap m_invocationHandlers;
LanguageInvokerThreadMap m_scripts;
+ CLanguageInvokerThreadPtr m_lastInvokerThread;
+ int m_lastPluginHandle;
+
std::map<std::string, int> m_scriptPaths;
int m_nextId = 0;
mutable CCriticalSection m_critSection;
diff --git a/xbmc/interfaces/python/PythonInvoker.cpp b/xbmc/interfaces/python/PythonInvoker.cpp
index 4342e05dcd..9019585b0f 100644
--- a/xbmc/interfaces/python/PythonInvoker.cpp
+++ b/xbmc/interfaces/python/PythonInvoker.cpp
@@ -23,8 +23,6 @@
#include "windowing/GraphicContext.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
-#include "interfaces/legacy/Addon.h"
-#include "interfaces/python/LanguageHook.h"
#include "interfaces/python/PyContext.h"
#include "interfaces/python/pythreadstate.h"
#include "interfaces/python/swig.h"
@@ -110,7 +108,7 @@ CPythonInvoker::~CPythonInvoker()
if (GetId() < 0)
return;
- if (GetState() < InvokerStateDone)
+ if (GetState() < InvokerStateExecutionDone)
CLog::Log(LOGDEBUG, "CPythonInvoker(%d): waiting for python thread \"%s\" to stop",
GetId(), (!m_sourceFile.empty() ? m_sourceFile.c_str() : "unknown script"));
Stop(true);
@@ -152,6 +150,7 @@ bool CPythonInvoker::execute(const std::string& script, const std::vector<std::w
{
// copy the code/script into a local string buffer
m_sourceFile = script;
+ m_pythonPath.clear();
// copy the arguments into a local buffer
unsigned int argc = arguments.size();
@@ -160,113 +159,129 @@ bool CPythonInvoker::execute(const std::string& script, const std::vector<std::w
CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): start processing", GetId(), m_sourceFile.c_str());
- // get the global lock
- extern PyThreadState* savestate;
- PyEval_RestoreThread(savestate);
- PyThreadState* state = Py_NewInterpreter();
- if (state == NULL)
+ std::string realFilename(CSpecialProtocol::TranslatePath(m_sourceFile));
+ std::string scriptDir = URIUtils::GetDirectory(realFilename);
+ URIUtils::RemoveSlashAtEnd(scriptDir);
+
+ // set m_threadState if it's not set.
+ PyThreadState* l_threadState = nullptr;
+ bool newInterp = false;
{
- PyEval_ReleaseLock();
- CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): FAILED to get thread state!", GetId(), m_sourceFile.c_str());
- return false;
+ if (!m_threadState) {
+ // TODO: Re-write everything.
+ // this is a TOTAL hack. We need the GIL but we need to borrow a PyThreadState in order to get it
+ // as of Python 3.2 since PyEval_AcquireLock is deprecated
+ extern PyThreadState* savestate;
+ PyEval_RestoreThread(savestate);
+ l_threadState = Py_NewInterpreter();
+ PyEval_ReleaseThread(l_threadState);
+ if (l_threadState == NULL)
+ {
+ CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): FAILED to get thread m_threadState!", GetId(), m_sourceFile.c_str());
+ return false;
+ }
+ newInterp = true;
+ } else
+ l_threadState = m_threadState;
}
- // swap in my thread state
- PyThreadState_Swap(state);
- XBMCAddon::AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> languageHook(new XBMCAddon::Python::PythonLanguageHook(state->interp));
- languageHook->RegisterMe();
+ // get the GIL
+ PyEval_RestoreThread(l_threadState);
+ if (newInterp)
+ {
+ m_languageHook = new XBMCAddon::Python::PythonLanguageHook(l_threadState->interp);
+ m_languageHook->RegisterMe();
- onInitialization();
- setState(InvokerStateInitialized);
+ onInitialization();
+ setState(InvokerStateInitialized);
- std::string realFilename(CSpecialProtocol::TranslatePath(m_sourceFile));
- if (realFilename == m_sourceFile)
- CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): the source file to load is \"%s\"", GetId(), m_sourceFile.c_str(), m_sourceFile.c_str());
- else
- CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): the source file to load is \"%s\" (\"%s\")", GetId(), m_sourceFile.c_str(), m_sourceFile.c_str(), realFilename.c_str());
+ if (realFilename == m_sourceFile)
+ CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): the source file to load is \"%s\"", GetId(), m_sourceFile.c_str(), m_sourceFile.c_str());
+ else
+ CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): the source file to load is \"%s\" (\"%s\")", GetId(), m_sourceFile.c_str(), m_sourceFile.c_str(), realFilename.c_str());
- // get path from script file name and add python path's
- // this is used for python so it will search modules from script path first
- std::string scriptDir = URIUtils::GetDirectory(realFilename);
- URIUtils::RemoveSlashAtEnd(scriptDir);
- addPath(scriptDir);
+ // get path from script file name and add python path's
+ // this is used for python so it will search modules from script path first
+ addPath(scriptDir);
- // add all addon module dependencies to path
- if (m_addon)
- {
- std::set<std::string> paths;
- getAddonModuleDeps(m_addon, paths);
- for (const auto& it : paths)
- addPath(it);
- }
- else
- { // for backwards compatibility.
- // we don't have any addon so just add all addon modules installed
- CLog::Log(LOGWARNING, "CPythonInvoker(%d): Script invoked without an addon. Adding all addon "
+ // add all addon module dependencies to path
+ if (m_addon)
+ {
+ std::set<std::string> paths;
+ getAddonModuleDeps(m_addon, paths);
+ for (const auto& it : paths)
+ addPath(it);
+ }
+ else
+ { // for backwards compatibility.
+ // we don't have any addon so just add all addon modules installed
+ CLog::Log(LOGWARNING, "CPythonInvoker(%d): Script invoked without an addon. Adding all addon "
"modules installed to python path as fallback. This behaviour will be removed in future "
"version.", GetId());
- ADDON::VECADDONS addons;
- CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON::ADDON_SCRIPT_MODULE);
- for (const auto& addon : addons)
- addPath(CSpecialProtocol::TranslatePath(addon->LibPath()));
- }
+ ADDON::VECADDONS addons;
+ CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON::ADDON_SCRIPT_MODULE);
+ for (unsigned int i = 0; i < addons.size(); ++i)
+ addPath(CSpecialProtocol::TranslatePath(addons[i]->LibPath()));
+ }
- // we want to use sys.path so it includes site-packages
- // if this fails, default to using Py_GetPath
- PyObject* sysMod(PyImport_ImportModule("sys")); // must call Py_DECREF when finished
- PyObject* sysModDict(PyModule_GetDict(sysMod)); // borrowed ref, no need to delete
- PyObject* pathObj(PyDict_GetItemString(sysModDict, "path")); // borrowed ref, no need to delete
+ // we want to use sys.path so it includes site-packages
+ // if this fails, default to using Py_GetPath
+ PyObject *sysMod(PyImport_ImportModule("sys")); // must call Py_DECREF when finished
+ PyObject *sysModDict(PyModule_GetDict(sysMod)); // borrowed ref, no need to delete
+ PyObject *pathObj(PyDict_GetItemString(sysModDict, "path")); // borrowed ref, no need to delete
- if (pathObj != NULL && PyList_Check(pathObj))
- {
- for (int i = 0; i < PyList_Size(pathObj); i++)
+ if (pathObj != NULL && PyList_Check(pathObj))
{
- PyObject *e = PyList_GetItem(pathObj, i); // borrowed ref, no need to delete
- if (e != NULL && PyUnicode_Check(e))
- addPath(PyUnicode_AsUTF8(e)); // returns internal data, don't delete or modify
+ for (int i = 0; i < PyList_Size(pathObj); i++) {
+ PyObject *e = PyList_GetItem(pathObj, i); // borrowed ref, no need to delete
+ if (e != NULL && PyUnicode_Check(e))
+ addPath(PyUnicode_AsUTF8(e)); // returns internal data, don't delete or modify
+ }
+ }
+ else
+ {
+ std::string GetPath;
+ g_charsetConverter.wToUTF8(Py_GetPath(), GetPath);
+ addPath(GetPath);
}
- }
- else
- {
- std::string GetPath;
- g_charsetConverter.wToUTF8(Py_GetPath(), GetPath);
- addPath(GetPath);
- }
-
- Py_DECREF(sysMod); // release ref to sysMod
- // set current directory and python's path.
- PySys_SetArgv(argc, &argv[0]);
+ Py_DECREF(sysMod); // release ref to sysMod
#ifdef TARGET_WINDOWS
- std::string pyPathUtf8;
- g_charsetConverter.systemToUtf8(m_pythonPath, pyPathUtf8, false);
- CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): setting the Python path to %s", GetId(), m_sourceFile.c_str(), pyPathUtf8.c_str());
+ std::string pyPathUtf8;
+ g_charsetConverter.systemToUtf8(m_pythonPath, pyPathUtf8, false);
+ CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): setting the Python path to %s", GetId(), m_sourceFile.c_str(), pyPathUtf8.c_str());
#else // ! TARGET_WINDOWS
- CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): setting the Python path to %s", GetId(), m_sourceFile.c_str(), m_pythonPath.c_str());
+ CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): setting the Python path to %s", GetId(), m_sourceFile.c_str(), m_pythonPath.c_str());
#endif // ! TARGET_WINDOWS
- std::wstring pypath;
- g_charsetConverter.utf8ToW(m_pythonPath, pypath);
- PySys_SetPath(pypath.c_str());
+ std::wstring pypath;
+ g_charsetConverter.utf8ToW(m_pythonPath, pypath);
+ PySys_SetPath(pypath.c_str());
+
+ { // set the m_threadState to this new interp
+ CSingleLock lockMe(m_critical);
+ m_threadState = l_threadState;
+ }
+ }
+ else
+ // swap in my thread m_threadState
+ PyThreadState_Swap(m_threadState);
+
+ // set current directory and python's path.
+ PySys_SetArgv(argc, &argv[0]);
CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): entering source directory %s", GetId(), m_sourceFile.c_str(), scriptDir.c_str());
PyObject* module = PyImport_AddModule("__main__");
PyObject* moduleDict = PyModule_GetDict(module);
- // when we are done initing we store thread state so we can be aborted
- PyThreadState_Swap(NULL);
- PyEval_ReleaseLock();
-
// we need to check if we was asked to abort before we had inited
bool stopping = false;
- { CSingleLock lock(m_critical);
- m_threadState = state;
+ {
+ GilSafeSingleLock lock(m_critical);
stopping = m_stop;
}
- PyEval_AcquireThread(state);
-
bool failed = false;
std::string exceptionType, exceptionValue, exceptionTraceback;
if (!stopping)
@@ -316,17 +331,17 @@ bool CPythonInvoker::execute(const std::string& script, const std::vector<std::w
}
}
- bool systemExitThrown = false;
+ m_systemExitThrown = false;
InvokerState stateToSet;
if (!failed && !PyErr_Occurred())
{
CLog::Log(LOGINFO, "CPythonInvoker(%d, %s): script successfully run", GetId(), m_sourceFile.c_str());
- stateToSet = InvokerStateDone;
+ stateToSet = InvokerStateScriptDone;
onSuccess();
}
else if (PyErr_ExceptionMatches(PyExc_SystemExit))
{
- systemExitThrown = true;
+ m_systemExitThrown = true;
CLog::Log(LOGINFO, "CPythonInvoker(%d, %s): script aborted", GetId(), m_sourceFile.c_str());
stateToSet = InvokerStateFailed;
onAbort();
@@ -351,6 +366,7 @@ bool CPythonInvoker::execute(const std::string& script, const std::vector<std::w
onError(exceptionType, exceptionValue, exceptionTraceback);
}
+ CSingleLock lock(m_critical);
// no need to do anything else because the script has already stopped
if (failed)
{
@@ -358,74 +374,40 @@ bool CPythonInvoker::execute(const std::string& script, const std::vector<std::w
return true;
}
- PyObject* m = PyImport_AddModule("xbmc");
- if (m == NULL || PyObject_SetAttrString(m, "abortRequested", PyBool_FromLong(1)))
- CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failed to set abortRequested", GetId(), m_sourceFile.c_str());
-
- // make sure all sub threads have finished
- for (PyThreadState* s = state->interp->tstate_head, *old = NULL; s;)
+ if (m_threadState)
{
- if (s == state)
- {
- s = s->next;
- continue;
- }
- if (old != s)
- {
- CLog::Log(LOGINFO, "CPythonInvoker(%d, %s): waiting on thread %" PRIu64, GetId(), m_sourceFile.c_str(), (uint64_t)s->thread_id);
- old = s;
- }
-
- CPyThreadState pyState;
- Sleep(100);
- pyState.Restore();
-
- s = state->interp->tstate_head;
- }
+ PyObject *m = PyImport_AddModule("xbmc");
+ if (m == NULL || PyObject_SetAttrString(m, "abortRequested", PyBool_FromLong(1)))
+ CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failed to set abortRequested", GetId(), m_sourceFile.c_str());
- // pending calls must be cleared out
- XBMCAddon::RetardedAsyncCallbackHandler::clearPendingCalls(state);
+ // make sure all sub threads have finished
+ for (PyThreadState *old = nullptr; m_threadState != nullptr;)
+ {
+ PyThreadState *s = m_threadState->interp->tstate_head;
+ for (; s && s == m_threadState;)
+ s = s->next;
- PyThreadState_Swap(NULL);
- PyEval_ReleaseLock();
+ if (!s)
+ break;
- // set stopped event - this allows ::stop to run and kill remaining threads
- // this event has to be fired without holding m_critical
- // also the GIL (PyEval_RestoreThread) must not be held
- // if not obeyed there is still no deadlock because ::stop waits with timeout (smart one!)
- m_stoppedEvent.Set();
+ if (old != s)
+ {
+ CLog::Log(LOGINFO, "CPythonInvoker(%d, %s): waiting on thread %" PRIu64, GetId(), m_sourceFile.c_str(), (uint64_t)s->thread_id);
+ old = s;
+ }
- { CSingleLock lock(m_critical);
- m_threadState = NULL;
+ lock.Leave();
+ CPyThreadState pyState;
+ Sleep(100);
+ pyState.Restore();
+ lock.Enter();
+ }
}
- PyEval_RestoreThread(state);
-
- onDeinitialization();
-
- // run the gc before finishing
- //
- // if the script exited by throwing a SystemExit exception then going back
- // into the interpreter causes this python bug to get hit:
- // http://bugs.python.org/issue10582
- // and that causes major failures. So we are not going to go back in
- // to run the GC if that's the case.
- if (!m_stop && languageHook->HasRegisteredAddonClasses() && !systemExitThrown &&
- PyRun_SimpleString(GC_SCRIPT) == -1)
- CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failed to run the gc to clean up after running prior to shutting down the Interpreter", GetId(), m_sourceFile.c_str());
-
- Py_EndInterpreter(state);
-
- // If we still have objects left around, produce an error message detailing what's been left behind
- if (languageHook->HasRegisteredAddonClasses())
- CLog::Log(LOGWARNING, "CPythonInvoker(%d, %s): the python script \"%s\" has left several "
- "classes in memory that we couldn't clean up. The classes include: %s",
- GetId(), m_sourceFile.c_str(), m_sourceFile.c_str(), getListOfAddonClassesAsString(languageHook).c_str());
-
- // unregister the language hook
- languageHook->UnregisterMe();
+ // pending calls must be cleared out
+ XBMCAddon::RetardedAsyncCallbackHandler::clearPendingCalls(m_threadState);
- PyEval_ReleaseLock();
+ PyEval_ReleaseThread(m_threadState);
setState(stateToSet);
@@ -467,28 +449,35 @@ bool CPythonInvoker::stop(bool abort)
CSingleLock lock(m_critical);
m_stop = true;
- if (!IsRunning())
+ if (!IsRunning() && !m_threadState)
return false;
- setState(InvokerStateStopping);
-
if (m_threadState != NULL)
{
- PyEval_RestoreThread((PyThreadState*)m_threadState);
- PyThreadState* old = PyThreadState_Swap((PyThreadState*)m_threadState);
+ if (IsRunning())
+ {
+ setState(InvokerStateStopping);
+ lock.Leave();
- //tell xbmc.Monitor to call onAbortRequested()
- if (m_addon != NULL)
- onAbortRequested();
+ PyEval_RestoreThread((PyThreadState*)m_threadState);
- PyObject* m;
- m = PyImport_AddModule("xbmc");
- if (m == NULL || PyObject_SetAttrString(m, "abortRequested", PyBool_FromLong(1)))
- CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failed to set abortRequested", GetId(), m_sourceFile.c_str());
+ //tell xbmc.Monitor to call onAbortRequested()
+ if (m_addon)
+ {
+ CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): trigger Monitor abort request", GetId(), m_sourceFile.c_str());
+ onAbortRequested();
+ }
- PyThreadState_Swap(old);
- old = NULL;
- PyEval_ReleaseLock();
+ PyObject *m;
+ m = PyImport_AddModule("xbmc");
+ if (m == NULL || PyObject_SetAttrString(m, "abortRequested", PyBool_FromLong(1)))
+ CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failed to set abortRequested", GetId(), m_sourceFile.c_str());
+
+ PyEval_ReleaseThread(m_threadState);
+ }
+ else
+ //Release the lock while waiting for threads to finish
+ lock.Leave();
XbmcThreads::EndTime timeout(PYTHON_SCRIPT_TIMEOUT);
while (!m_stoppedEvent.WaitMSec(15))
@@ -508,22 +497,24 @@ bool CPythonInvoker::stop(bool abort)
}
}
+ lock.Enter();
+
+ setState(InvokerStateExecutionDone);
+
// Useful for add-on performance metrics
if (!timeout.IsTimePast())
CLog::Log(LOGDEBUG, "CPythonInvoker(%d, %s): script termination took %dms", GetId(), m_sourceFile.c_str(), PYTHON_SCRIPT_TIMEOUT - timeout.MillisLeft());
- // everything which didn't exit by now gets killed
- {
- // grabbing the PyLock while holding the m_critical is asking for a deadlock
- CSingleExit ex2(m_critical);
- PyEval_RestoreThread((PyThreadState*)m_threadState);
- }
-
// Since we released the m_critical it's possible that the state is cleaned up
// so we need to recheck for m_threadState == NULL
if (m_threadState != NULL)
{
- old = PyThreadState_Swap((PyThreadState*)m_threadState);
+ {
+ // grabbing the PyLock while holding the m_critical is asking for a deadlock
+ CSingleExit ex2(m_critical);
+ PyEval_RestoreThread((PyThreadState*)m_threadState);
+ }
+
for (PyThreadState* state = ((PyThreadState*)m_threadState)->interp->tstate_head; state; state = state->next)
{
// Raise a SystemExit exception in python threads
@@ -534,18 +525,67 @@ bool CPythonInvoker::stop(bool abort)
// If a dialog entered its doModal(), we need to wake it to see the exception
pulseGlobalEvent();
- }
-
- if (old != NULL)
- PyThreadState_Swap(old);
+ PyEval_ReleaseThread(m_threadState);
+ m_threadState = nullptr;
+ }
lock.Leave();
- PyEval_ReleaseLock();
+
+ setState(InvokerStateFailed);
}
return true;
}
+// Always called from Invoker thread
+void CPythonInvoker::onExecutionDone()
+{
+ CSingleLock lock(m_critical);
+ if (m_threadState != NULL)
+ {
+ CLog::Log(LOGDEBUG, "%s(%d, %s)", __FUNCTION__, GetId(), m_sourceFile.c_str());
+
+ PyEval_RestoreThread(m_threadState);
+
+ onDeinitialization();
+
+ // run the gc before finishing
+ //
+ // if the script exited by throwing a SystemExit exception then going back
+ // into the interpreter causes this python bug to get hit:
+ // http://bugs.python.org/issue10582
+ // and that causes major failures. So we are not going to go back in
+ // to run the GC if that's the case.
+ if (!m_stop && m_languageHook->HasRegisteredAddonClasses() && !m_systemExitThrown &&
+ PyRun_SimpleString(GC_SCRIPT) == -1)
+ CLog::Log(LOGERROR, "CPythonInvoker(%d, %s): failed to run the gc to clean up after running prior to shutting down the Interpreter", GetId(), m_sourceFile.c_str());
+
+ Py_EndInterpreter(m_threadState);
+
+ // If we still have objects left around, produce an error message detailing what's been left behind
+ if (m_languageHook->HasRegisteredAddonClasses())
+ CLog::Log(LOGWARNING, "CPythonInvoker(%d, %s): the python script \"%s\" has left several "
+ "classes in memory that we couldn't clean up. The classes include: %s",
+ GetId(), m_sourceFile.c_str(), m_sourceFile.c_str(), getListOfAddonClassesAsString(m_languageHook).c_str());
+
+ // unregister the language hook
+ m_languageHook->UnregisterMe();
+
+ PyEval_ReleaseLock();
+
+ // set stopped event - this allows ::stop to run and kill remaining threads
+ // this event has to be fired without holding m_critical
+ // also the GIL (PyEval_AcquireLock) must not be held
+ // if not obeyed there is still no deadlock because ::stop waits with timeout (smart one!)
+ m_stoppedEvent.Set();
+
+ m_threadState = nullptr;
+
+ setState(InvokerStateExecutionDone);
+ }
+ ILanguageInvoker::onExecutionDone();
+}
+
void CPythonInvoker::onExecutionFailed()
{
PyThreadState_Swap(NULL);
diff --git a/xbmc/interfaces/python/PythonInvoker.h b/xbmc/interfaces/python/PythonInvoker.h
index b597e16b0a..b494ff2478 100644
--- a/xbmc/interfaces/python/PythonInvoker.h
+++ b/xbmc/interfaces/python/PythonInvoker.h
@@ -9,6 +9,8 @@
#pragma once
#include "interfaces/generic/ILanguageInvoker.h"
+#include "interfaces/legacy/Addon.h"
+#include "interfaces/python/LanguageHook.h"
#include "threads/CriticalSection.h"
#include "threads/Event.h"
@@ -17,6 +19,7 @@
#include <vector>
typedef struct _object PyObject;
+struct _ts;
class CPythonInvoker : public ILanguageInvoker
{
@@ -35,6 +38,7 @@ protected:
bool execute(const std::string &script, const std::vector<std::string> &arguments) override;
virtual void executeScript(FILE* fp, const std::string& script, PyObject* moduleDict);
bool stop(bool abort) override;
+ void onExecutionDone() override;
void onExecutionFailed() override;
// custom virtual methods
@@ -61,9 +65,12 @@ private:
FILE* PyFile_AsFileWithMode(PyObject* py_file, const char* mode);
std::string m_pythonPath;
- void* m_threadState;
+ _ts *m_threadState;
bool m_stop;
CEvent m_stoppedEvent;
+ XBMCAddon::AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> m_languageHook;
+ bool m_systemExitThrown = false;
+
static CCriticalSection s_critical;
};
diff --git a/xbmc/interfaces/python/XBPython.cpp b/xbmc/interfaces/python/XBPython.cpp
index b54c779c14..3dfdfb5b8d 100644
--- a/xbmc/interfaces/python/XBPython.cpp
+++ b/xbmc/interfaces/python/XBPython.cpp
@@ -647,7 +647,7 @@ void XBPython::OnScriptAbortRequested(ILanguageInvoker *invoker)
}
}
-void XBPython::OnScriptEnded(ILanguageInvoker* invoker)
+void XBPython::OnExecutionEnded(ILanguageInvoker *invoker)
{
CSingleLock lock(m_vecPyList);
PyList::iterator it = m_vecPyList.begin();
@@ -656,9 +656,9 @@ void XBPython::OnScriptEnded(ILanguageInvoker* invoker)
if (it->id == invoker->GetId())
{
if (it->pyThread->IsStopping())
- CLog::Log(LOGINFO, "Python script interrupted by user");
+ CLog::Log(LOGINFO, "Python interpreter interrupted by user");
else
- CLog::Log(LOGINFO, "Python script stopped");
+ CLog::Log(LOGINFO, "Python interpreter stopped");
it->bDone = true;
}
++it;
diff --git a/xbmc/interfaces/python/XBPython.h b/xbmc/interfaces/python/XBPython.h
index d6121052d3..2bad14d90b 100644
--- a/xbmc/interfaces/python/XBPython.h
+++ b/xbmc/interfaces/python/XBPython.h
@@ -91,7 +91,7 @@ public:
bool OnScriptInitialized(ILanguageInvoker *invoker) override;
void OnScriptStarted(ILanguageInvoker *invoker) override;
void OnScriptAbortRequested(ILanguageInvoker *invoker) override;
- void OnScriptEnded(ILanguageInvoker* invoker) override;
+ void OnExecutionEnded(ILanguageInvoker *invoker) override;
void OnScriptFinalized(ILanguageInvoker *invoker) override;
ILanguageInvoker* CreateInvoker() override;