diff options
author | Jim Carroll <thecarrolls@jiminger.com> | 2012-12-06 09:37:28 -0800 |
---|---|---|
committer | Jim Carroll <thecarrolls@jiminger.com> | 2012-12-06 09:37:28 -0800 |
commit | 8cd02887839fa1db12a676216fe6af7878ac008a (patch) | |
tree | 816b74f7f7d24af364e565f7cf1b3f1b690d0fb2 | |
parent | 6555ade1978cc1e740931bf8b8fbb4c3da2bfea8 (diff) | |
parent | 7fe785597649b4666fb384bb86fe0c0d429dfc44 (diff) |
Merge pull request #1889 from jimfcarroll/python-cleanup-fix
Python "leak" fix. Closes #13624
-rw-r--r-- | xbmc/interfaces/legacy/Addon.cpp | 4 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/AddonCallback.h | 2 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/AddonClass.cpp | 4 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/AddonClass.h | 3 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/CallbackHandler.cpp | 4 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/CallbackHandler.h | 4 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/LanguageHook.cpp | 6 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/LanguageHook.h | 54 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/ModuleXbmc.cpp | 2 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/Monitor.cpp | 6 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/Player.cpp | 4 | ||||
-rw-r--r-- | xbmc/interfaces/legacy/Window.cpp | 6 | ||||
-rw-r--r-- | xbmc/interfaces/python/CallbackHandler.cpp | 22 | ||||
-rw-r--r-- | xbmc/interfaces/python/CallbackHandler.h | 4 | ||||
-rw-r--r-- | xbmc/interfaces/python/LanguageHook.cpp | 100 | ||||
-rw-r--r-- | xbmc/interfaces/python/LanguageHook.h | 50 | ||||
-rw-r--r-- | xbmc/interfaces/python/PythonSwig.cpp.template | 2 | ||||
-rw-r--r-- | xbmc/interfaces/python/XBPyThread.cpp | 54 | ||||
-rw-r--r-- | xbmc/interfaces/python/swig.cpp | 87 | ||||
-rw-r--r-- | xbmc/interfaces/python/swig.h | 40 |
20 files changed, 337 insertions, 121 deletions
diff --git a/xbmc/interfaces/legacy/Addon.cpp b/xbmc/interfaces/legacy/Addon.cpp index bc44dd9473..1c3574aa33 100644 --- a/xbmc/interfaces/legacy/Addon.cpp +++ b/xbmc/interfaces/legacy/Addon.cpp @@ -33,9 +33,9 @@ namespace XBMCAddon { namespace xbmcaddon { - String Addon::getDefaultId() { return languageHook == NULL ? emptyString : languageHook->getAddonId(); } + String Addon::getDefaultId() { return languageHook == NULL ? emptyString : languageHook->GetAddonId(); } - String Addon::getAddonVersion() { return languageHook == NULL ? emptyString : languageHook->getAddonVersion(); } + String Addon::getAddonVersion() { return languageHook == NULL ? emptyString : languageHook->GetAddonVersion(); } Addon::Addon(const char* cid) throw (AddonException) : AddonClass("Addon") { diff --git a/xbmc/interfaces/legacy/AddonCallback.h b/xbmc/interfaces/legacy/AddonCallback.h index 159cb171a4..366fcd63e8 100644 --- a/xbmc/interfaces/legacy/AddonCallback.h +++ b/xbmc/interfaces/legacy/AddonCallback.h @@ -45,7 +45,7 @@ namespace XBMCAddon { // if there is a LanguageHook, it should be set already. if (languageHook != NULL) - setHandler(languageHook->getCallbackHandler()); + setHandler(languageHook->GetCallbackHandler()); } virtual ~AddonCallback(); diff --git a/xbmc/interfaces/legacy/AddonClass.cpp b/xbmc/interfaces/legacy/AddonClass.cpp index eda9a042c4..fb5319ec12 100644 --- a/xbmc/interfaces/legacy/AddonClass.cpp +++ b/xbmc/interfaces/legacy/AddonClass.cpp @@ -61,7 +61,7 @@ namespace XBMCAddon #endif // check to see if we have a language hook that was prepared for this instantiation - languageHook = LanguageHook::getLanguageHook(); + languageHook = LanguageHook::GetLanguageHook(); if (languageHook != NULL) { languageHook->Acquire(); @@ -70,7 +70,7 @@ namespace XBMCAddon // this AddonClass (actually - its subclass - but whatever). So we // will now reset the Tls. This avoids issues if the constructor of the // subclass throws an exception. - LanguageHook::clearLanguageHook(); + LanguageHook::ClearLanguageHook(); } } diff --git a/xbmc/interfaces/legacy/AddonClass.h b/xbmc/interfaces/legacy/AddonClass.h index d9e991c155..ec76bd4748 100644 --- a/xbmc/interfaces/legacy/AddonClass.h +++ b/xbmc/interfaces/legacy/AddonClass.h @@ -101,6 +101,9 @@ namespace XBMCAddon AddonClass(const char* classname); virtual ~AddonClass(); + inline const String& GetClassname() const { return classname; } + inline LanguageHook* GetLanguageHook() { return languageHook; } + /** * This method should be called while holding a Synchronize * on the object. It will prevent the deallocation during diff --git a/xbmc/interfaces/legacy/CallbackHandler.cpp b/xbmc/interfaces/legacy/CallbackHandler.cpp index 0cedb85c7c..22ad958563 100644 --- a/xbmc/interfaces/legacy/CallbackHandler.cpp +++ b/xbmc/interfaces/legacy/CallbackHandler.cpp @@ -84,7 +84,7 @@ namespace XBMCAddon AddonClass::Ref<AsynchCallbackMessage> p(*iter); // only call when we are in the right thread state - if(p->handler->isThreadStateOk()) + if(p->handler->isStateOk(p->cb->getObject())) { // remove it from the queue. No matter what we're done with // this. Even if it doesn't execute for some reason. @@ -140,7 +140,7 @@ namespace XBMCAddon { AddonClass::Ref<AsynchCallbackMessage> p(*iter); - if(p->handler->shouldRemoveCallback(userData)) + if(p->handler->shouldRemoveCallback(p->cb->getObject(),userData)) { #ifdef ENABLE_TRACE_API CLog::Log(LOGDEBUG,"%sNEWADDON removing callback 0x%lx for PyThreadState 0x%lx from queue", _tg.getSpaces(),(long)(p->cb.get()) ,(long)userData); diff --git a/xbmc/interfaces/legacy/CallbackHandler.h b/xbmc/interfaces/legacy/CallbackHandler.h index 81793fdd04..b4d6ff5c16 100644 --- a/xbmc/interfaces/legacy/CallbackHandler.h +++ b/xbmc/interfaces/legacy/CallbackHandler.h @@ -64,8 +64,8 @@ namespace XBMCAddon static void makePendingCalls(); static void clearPendingCalls(void* userData); - virtual bool isThreadStateOk() = 0; - virtual bool shouldRemoveCallback(void* userData) = 0; + virtual bool isStateOk(AddonClass* obj) = 0; + virtual bool shouldRemoveCallback(AddonClass* obj, void* userData) = 0; }; } diff --git a/xbmc/interfaces/legacy/LanguageHook.cpp b/xbmc/interfaces/legacy/LanguageHook.cpp index 65db0853a7..3f0af14c8f 100644 --- a/xbmc/interfaces/legacy/LanguageHook.cpp +++ b/xbmc/interfaces/legacy/LanguageHook.cpp @@ -33,19 +33,19 @@ namespace XBMCAddon static bool threadLocalInitilialized = false; static xbmcutil::InitFlag initer(threadLocalInitilialized); - void LanguageHook::setLanguageHook(LanguageHook* languageHook) + void LanguageHook::SetLanguageHook(LanguageHook* languageHook) { TRACE; languageHook->Acquire(); addonLanguageHookTls.set(languageHook); } - LanguageHook* LanguageHook::getLanguageHook() + LanguageHook* LanguageHook::GetLanguageHook() { return threadLocalInitilialized ? addonLanguageHookTls.get() : NULL; } - void LanguageHook::clearLanguageHook() + void LanguageHook::ClearLanguageHook() { LanguageHook* lh = addonLanguageHookTls.get(); addonLanguageHookTls.set(NULL); diff --git a/xbmc/interfaces/legacy/LanguageHook.h b/xbmc/interfaces/legacy/LanguageHook.h index ae671bd162..e6873ada5b 100644 --- a/xbmc/interfaces/legacy/LanguageHook.h +++ b/xbmc/interfaces/legacy/LanguageHook.h @@ -58,7 +58,7 @@ namespace XBMCAddon * Python to run by using Py_BEGIN_ALLOW_THREADS. This is * the place to put that functionality */ - virtual void delayedCallOpen() { } + virtual void DelayedCallOpen() { } /** * If the scripting language needs special handling for calls @@ -72,15 +72,15 @@ namespace XBMCAddon * state using Py_END_ALLOW_THREADS. This is the place to put * that functionality */ - virtual void delayedCallClose() { } + virtual void DelayedCallClose() { } - virtual void makePendingCalls() {} + virtual void MakePendingCalls() {} /** * For scripting languages that need a global callback handler, this * method should be overloaded to supply one. */ - virtual CallbackHandler* getCallbackHandler() { return NULL; } + virtual CallbackHandler* GetCallbackHandler() { return NULL; } /** * This is a callback method that can be overriden to receive a callback @@ -89,7 +89,7 @@ namespace XBMCAddon * cannot assume the subclasses have been built or that calling a * virtual function on the AddonClass will work as expected. */ - virtual void constructing(AddonClass* beingConstructed) { } + virtual void Constructing(AddonClass* beingConstructed) { } /** * This is a callback method that can be overriden to receive a callback @@ -98,7 +98,7 @@ namespace XBMCAddon * should assume the subclasses have been torn down and that calling a * virtual function on the AddonClass will not work as expected. */ - virtual void destructing(AddonClass* beingDestructed) { } + virtual void Destructing(AddonClass* beingDestructed) { } /** * This method should be done a different way but since the only other way @@ -110,23 +110,23 @@ namespace XBMCAddon * to use scripting language specific calls. So until I figure out a * better way to do this, this is how I need to retrieve it. */ - virtual String getAddonId() { return emptyString; } - virtual String getAddonVersion() { return emptyString; } - - virtual void registerPlayerCallback(IPlayerCallback* player) = 0; - virtual void unregisterPlayerCallback(IPlayerCallback* player) = 0; - virtual void registerMonitorCallback(XBMCAddon::xbmc::Monitor* player) = 0; - virtual void unregisterMonitorCallback(XBMCAddon::xbmc::Monitor* player) = 0; - virtual bool waitForEvent(CEvent& hEvent, unsigned int milliseconds) = 0; - - static void setLanguageHook(LanguageHook* languageHook); - static LanguageHook* getLanguageHook(); - static void clearLanguageHook(); + virtual String GetAddonId() { return emptyString; } + virtual String GetAddonVersion() { return emptyString; } + + virtual void RegisterPlayerCallback(IPlayerCallback* player) = 0; + virtual void UnregisterPlayerCallback(IPlayerCallback* player) = 0; + virtual void RegisterMonitorCallback(XBMCAddon::xbmc::Monitor* player) = 0; + virtual void UnregisterMonitorCallback(XBMCAddon::xbmc::Monitor* player) = 0; + virtual bool WaitForEvent(CEvent& hEvent, unsigned int milliseconds) = 0; + + static void SetLanguageHook(LanguageHook* languageHook); + static LanguageHook* GetLanguageHook(); + static void ClearLanguageHook(); }; /** - * This class can be used to access the language hook's delayedCallOpen - * and delayedCallClose. It should be used whenever an API method + * This class can be used to access the language hook's DelayedCallOpen + * and DelayedCallClose. It should be used whenever an API method * is written such that it can block for an indefinite amount of time * since certain scripting languages (like Python) need to do extra * work for delayed calls (like free the python locks and handle @@ -139,15 +139,15 @@ namespace XBMCAddon public: inline DelayedCallGuard(LanguageHook* languageHook_) : languageHook(languageHook_), clearOnExit(false) - { if (languageHook) languageHook->delayedCallOpen(); } + { if (languageHook) languageHook->DelayedCallOpen(); } - inline DelayedCallGuard() : languageHook(LanguageHook::getLanguageHook()), clearOnExit(false) - { if (languageHook) languageHook->delayedCallOpen(); } + inline DelayedCallGuard() : languageHook(LanguageHook::GetLanguageHook()), clearOnExit(false) + { if (languageHook) languageHook->DelayedCallOpen(); } inline ~DelayedCallGuard() { - if (clearOnExit) LanguageHook::clearLanguageHook(); - if (languageHook) languageHook->delayedCallClose(); + if (clearOnExit) LanguageHook::ClearLanguageHook(); + if (languageHook) languageHook->DelayedCallClose(); } inline LanguageHook* getLanguageHook() { return languageHook; } @@ -156,8 +156,8 @@ namespace XBMCAddon class SetLanguageHookGuard { public: - inline SetLanguageHookGuard(LanguageHook* languageHook) { LanguageHook::setLanguageHook(languageHook); } - inline ~SetLanguageHookGuard() { LanguageHook::clearLanguageHook(); } + inline SetLanguageHookGuard(LanguageHook* languageHook) { LanguageHook::SetLanguageHook(languageHook); } + inline ~SetLanguageHookGuard() { LanguageHook::ClearLanguageHook(); } }; } diff --git a/xbmc/interfaces/legacy/ModuleXbmc.cpp b/xbmc/interfaces/legacy/ModuleXbmc.cpp index 5369a82117..e03dc76ab2 100644 --- a/xbmc/interfaces/legacy/ModuleXbmc.cpp +++ b/xbmc/interfaces/legacy/ModuleXbmc.cpp @@ -155,7 +155,7 @@ namespace XBMCAddon ::Sleep(nextSleep); } if (lh != NULL) - lh->makePendingCalls(); + lh->MakePendingCalls(); } } diff --git a/xbmc/interfaces/legacy/Monitor.cpp b/xbmc/interfaces/legacy/Monitor.cpp index 53b589c1a0..9cc1bcb0ea 100644 --- a/xbmc/interfaces/legacy/Monitor.cpp +++ b/xbmc/interfaces/legacy/Monitor.cpp @@ -29,8 +29,8 @@ namespace XBMCAddon { if (languageHook) { - Id = languageHook->getAddonId(); - languageHook->registerMonitorCallback(this); + Id = languageHook->GetAddonId(); + languageHook->RegisterMonitorCallback(this); } } @@ -42,7 +42,7 @@ namespace XBMCAddon if (languageHook) { DelayedCallGuard dc; - languageHook->unregisterMonitorCallback(this); + languageHook->UnregisterMonitorCallback(this); } } } diff --git a/xbmc/interfaces/legacy/Player.cpp b/xbmc/interfaces/legacy/Player.cpp index 449a24d82d..f644bfb7b3 100644 --- a/xbmc/interfaces/legacy/Player.cpp +++ b/xbmc/interfaces/legacy/Player.cpp @@ -50,7 +50,7 @@ namespace XBMCAddon if (languageHook) { DelayedCallGuard dc(languageHook); - languageHook->registerPlayerCallback(this); + languageHook->RegisterPlayerCallback(this); } } @@ -62,7 +62,7 @@ namespace XBMCAddon if (languageHook) { DelayedCallGuard dc(languageHook); - languageHook->unregisterPlayerCallback(this); + languageHook->UnregisterPlayerCallback(this); } } diff --git a/xbmc/interfaces/legacy/Window.cpp b/xbmc/interfaces/legacy/Window.cpp index 0a577e8af4..aae3631243 100644 --- a/xbmc/interfaces/legacy/Window.cpp +++ b/xbmc/interfaces/legacy/Window.cpp @@ -397,7 +397,7 @@ namespace XBMCAddon { TRACE; // DO NOT MAKE THIS A DELAYED CALL!!!! - bool ret = languageHook == NULL ? m_actionEvent.WaitMSec(milliseconds) : languageHook->waitForEvent(m_actionEvent,milliseconds); + bool ret = languageHook == NULL ? m_actionEvent.WaitMSec(milliseconds) : languageHook->WaitForEvent(m_actionEvent,milliseconds); if (ret) m_actionEvent.Reset(); return ret; @@ -706,7 +706,7 @@ namespace XBMCAddon // Window_Close(self, NULL); // break; // } - languageHook->makePendingCalls(); // MakePendingCalls + languageHook->MakePendingCalls(); // MakePendingCalls bool stillWaiting; do @@ -715,7 +715,7 @@ namespace XBMCAddon DelayedCallGuard dcguard(languageHook); stillWaiting = WaitForActionEvent(100) ? false : true; } - languageHook->makePendingCalls(); + languageHook->MakePendingCalls(); } while (stillWaiting); } } diff --git a/xbmc/interfaces/python/CallbackHandler.cpp b/xbmc/interfaces/python/CallbackHandler.cpp index 21d401ea0a..01707b964a 100644 --- a/xbmc/interfaces/python/CallbackHandler.cpp +++ b/xbmc/interfaces/python/CallbackHandler.cpp @@ -20,6 +20,7 @@ */ #include "CallbackHandler.h" +#include "LanguageHook.h" namespace XBMCAddon { @@ -42,10 +43,18 @@ namespace XBMCAddon * Now we are answering the question as to whether or not we are in the * PyThreadState that we were in when we started. */ - bool PythonCallbackHandler::isThreadStateOk() + bool PythonCallbackHandler::isStateOk(AddonClass* obj) { TRACE; - return objectThreadState == PyThreadState_Get(); + PyThreadState* state = PyThreadState_Get(); + if (objectThreadState == state) + { + // make sure the interpreter is still active. + AddonClass::Ref<XBMCAddon::Python::LanguageHook> lh(XBMCAddon::Python::LanguageHook::GetIfExists(state->interp)); + if (lh.isNotNull() && lh->HasRegisteredAddonClassInstance(obj) && lh.get() == obj->GetLanguageHook()) + return true; + } + return false; } /** @@ -55,10 +64,15 @@ namespace XBMCAddon * TODO: This is a stupid way to get this information back to the handler. * there should be a more language neutral means. */ - bool PythonCallbackHandler::shouldRemoveCallback(void* threadState) + bool PythonCallbackHandler::shouldRemoveCallback(AddonClass* obj, void* threadState) { TRACE; - return threadState == objectThreadState; + if (threadState == objectThreadState) + return true; + + // we also want to remove the callback if the language hook no longer exists. + // this is a belt-and-suspenders cleanup mechanism + return ! XBMCAddon::Python::LanguageHook::IsAddonClassInstanceRegistered(obj); } } } diff --git a/xbmc/interfaces/python/CallbackHandler.h b/xbmc/interfaces/python/CallbackHandler.h index 3c219c8fd8..e8f1836e49 100644 --- a/xbmc/interfaces/python/CallbackHandler.h +++ b/xbmc/interfaces/python/CallbackHandler.h @@ -44,8 +44,8 @@ namespace XBMCAddon * handling callbacks in the appropriate thread. */ PythonCallbackHandler(); - virtual bool isThreadStateOk(); - virtual bool shouldRemoveCallback(void* threadState); + virtual bool isStateOk(AddonClass* obj); + virtual bool shouldRemoveCallback(AddonClass* obj, void* threadState); }; } } diff --git a/xbmc/interfaces/python/LanguageHook.cpp b/xbmc/interfaces/python/LanguageHook.cpp index 788495fb70..63a4b3932a 100644 --- a/xbmc/interfaces/python/LanguageHook.cpp +++ b/xbmc/interfaces/python/LanguageHook.cpp @@ -21,6 +21,7 @@ #include "LanguageHook.h" +#include "CallbackHandler.h" #include "XBPython.h" #include "interfaces/legacy/AddonUtils.h" @@ -33,45 +34,65 @@ namespace XBMCAddon { static AddonClass::Ref<LanguageHook> instance; - static CCriticalSection ccrit; - static bool isInited = false; - static xbmcutil::InitFlag flag(isInited); + static CCriticalSection hooksMutex; + std::map<PyInterpreterState*,AddonClass::Ref<LanguageHook> > LanguageHook::hooks; // vtab instantiation - LanguageHook::~LanguageHook() { } + LanguageHook::~LanguageHook() + { + TRACE; + XBMCAddon::LanguageHook::deallocating(); + } - void LanguageHook::makePendingCalls() + void LanguageHook::MakePendingCalls() { + TRACE; PythonCallbackHandler::makePendingCalls(); } - void LanguageHook::delayedCallOpen() + void LanguageHook::DelayedCallOpen() { TRACE; PyGILLock::releaseGil(); } - void LanguageHook::delayedCallClose() + void LanguageHook::DelayedCallClose() { TRACE; PyGILLock::acquireGil(); } - LanguageHook* LanguageHook::getInstance() + void LanguageHook::RegisterMe() { - if (!isInited) // in this case we're being called from a static initializer - { - if (instance.isNull()) - instance = new LanguageHook(); - } - else + TRACE; + CSingleLock lock(hooksMutex); + hooks[m_interp] = AddonClass::Ref<LanguageHook>(this); + } + + void LanguageHook::UnregisterMe() + { + TRACE; + CSingleLock lock(hooksMutex); + hooks.erase(m_interp); + } + + AddonClass::Ref<LanguageHook> LanguageHook::GetIfExists(PyInterpreterState* interp) + { + TRACE; + CSingleLock lock(hooksMutex); + std::map<PyInterpreterState*,AddonClass::Ref<LanguageHook> >::iterator iter = hooks.find(interp); + return iter == hooks.end() ? AddonClass::Ref<LanguageHook>(NULL) : AddonClass::Ref<LanguageHook>(iter->second); + } + + bool LanguageHook::IsAddonClassInstanceRegistered(AddonClass* obj) + { + for (std::map<PyInterpreterState*,AddonClass::Ref<LanguageHook> >::iterator iter = hooks.begin(); + iter != hooks.end(); iter++) { - CSingleLock lock (ccrit); - if (instance.isNull()) - instance = new LanguageHook(); + if ((iter->second)->HasRegisteredAddonClassInstance(obj)) + return true; } - - return instance.get(); + return false; } /** @@ -86,13 +107,15 @@ namespace XBMCAddon * See PythonCallbackHandler for more details * See PythonCallbackHandler::PythonCallbackHandler for more details */ - XBMCAddon::CallbackHandler* LanguageHook::getCallbackHandler() + XBMCAddon::CallbackHandler* LanguageHook::GetCallbackHandler() { + TRACE; return new PythonCallbackHandler(); } - String LanguageHook::getAddonId() + String LanguageHook::GetAddonId() { + TRACE; const char* id = NULL; // Get a reference to the main module @@ -106,8 +129,9 @@ namespace XBMCAddon return id; } - String LanguageHook::getAddonVersion() + String LanguageHook::GetAddonVersion() { + TRACE; // Get a reference to the main module // and global dictionary PyObject* main_module = PyImport_AddModule((char*)"__main__"); @@ -119,14 +143,36 @@ namespace XBMCAddon return version; } - void LanguageHook::registerPlayerCallback(IPlayerCallback* player) { g_pythonParser.RegisterPythonPlayerCallBack(player); } - void LanguageHook::unregisterPlayerCallback(IPlayerCallback* player) { g_pythonParser.UnregisterPythonPlayerCallBack(player); } - void LanguageHook::registerMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) { g_pythonParser.RegisterPythonMonitorCallBack(monitor); } - void LanguageHook::unregisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) { g_pythonParser.UnregisterPythonMonitorCallBack(monitor); } + void LanguageHook::RegisterPlayerCallback(IPlayerCallback* player) { TRACE; g_pythonParser.RegisterPythonPlayerCallBack(player); } + void LanguageHook::UnregisterPlayerCallback(IPlayerCallback* player) { TRACE; g_pythonParser.UnregisterPythonPlayerCallBack(player); } + void LanguageHook::RegisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) { TRACE; g_pythonParser.RegisterPythonMonitorCallBack(monitor); } + void LanguageHook::UnregisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) { TRACE; g_pythonParser.UnregisterPythonMonitorCallBack(monitor); } - bool LanguageHook::waitForEvent(CEvent& hEvent, unsigned int milliseconds) + bool LanguageHook::WaitForEvent(CEvent& hEvent, unsigned int milliseconds) { + TRACE; return g_pythonParser.WaitForEvent(hEvent,milliseconds); } + + void LanguageHook::RegisterAddonClassInstance(AddonClass* obj) + { + TRACE; + Synchronize l(*this); + currentObjects.insert(obj); + } + + void LanguageHook::UnregisterAddonClassInstance(AddonClass* obj) + { + TRACE; + Synchronize l(*this); + currentObjects.erase(obj); + } + + bool LanguageHook::HasRegisteredAddonClassInstance(AddonClass* obj) + { + TRACE; + Synchronize l(*this); + return currentObjects.find(obj) != currentObjects.end(); + } } } diff --git a/xbmc/interfaces/python/LanguageHook.h b/xbmc/interfaces/python/LanguageHook.h index 34c1fcf063..63142d371b 100644 --- a/xbmc/interfaces/python/LanguageHook.h +++ b/xbmc/interfaces/python/LanguageHook.h @@ -28,10 +28,12 @@ #include <Python.h> #include "interfaces/legacy/LanguageHook.h" -#include "interfaces/python/CallbackHandler.h" #include "threads/ThreadLocal.h" #include "threads/Event.h" +#include <set> +#include <map> + namespace XBMCAddon { namespace Python @@ -45,15 +47,22 @@ namespace XBMCAddon */ class LanguageHook : public XBMCAddon::LanguageHook { - LanguageHook() : XBMCAddon::LanguageHook("Python::LanguageHook") { } + PyInterpreterState* m_interp; + CCriticalSection crit; + std::set<AddonClass*> currentObjects; + + static std::map<PyInterpreterState*,AddonClass::Ref<LanguageHook> > hooks; public: + inline LanguageHook(PyInterpreterState* interp) : + XBMCAddon::LanguageHook("Python::LanguageHook"), m_interp(interp) { } + virtual ~LanguageHook(); - virtual void delayedCallOpen(); - virtual void delayedCallClose(); - virtual void makePendingCalls(); + virtual void DelayedCallOpen(); + virtual void DelayedCallClose(); + virtual void MakePendingCalls(); /** * PythonCallbackHandler expects to be instantiated PER AddonClass instance @@ -67,18 +76,31 @@ namespace XBMCAddon * See PythonCallbackHandler for more details * See PythonCallbackHandler::PythonCallbackHandler for more details */ - virtual XBMCAddon::CallbackHandler* getCallbackHandler(); + virtual XBMCAddon::CallbackHandler* GetCallbackHandler(); + + virtual String GetAddonId(); + virtual String GetAddonVersion(); + + virtual void RegisterPlayerCallback(IPlayerCallback* player); + virtual void UnregisterPlayerCallback(IPlayerCallback* player); + virtual void RegisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor); + virtual void UnregisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor); + virtual bool WaitForEvent(CEvent& hEvent, unsigned int milliseconds); + + static AddonClass::Ref<LanguageHook> GetIfExists(PyInterpreterState* interp); + static bool IsAddonClassInstanceRegistered(AddonClass* obj); - virtual String getAddonId(); - virtual String getAddonVersion(); + void RegisterAddonClassInstance(AddonClass* obj); + void UnregisterAddonClassInstance(AddonClass* obj); + bool HasRegisteredAddonClassInstance(AddonClass* obj); + inline bool HasRegisteredAddonClasses() { Synchronize l(*this); return currentObjects.size() > 0; } - virtual void registerPlayerCallback(IPlayerCallback* player); - virtual void unregisterPlayerCallback(IPlayerCallback* player); - virtual void registerMonitorCallback(XBMCAddon::xbmc::Monitor* monitor); - virtual void unregisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor); - virtual bool waitForEvent(CEvent& hEvent, unsigned int milliseconds); + // You should hold the lock on the LanguageHook itself if you're + // going to do anything with the set that gets returned. + inline std::set<AddonClass*>& GetRegisteredAddonClasses() { return currentObjects; } - static LanguageHook* getInstance(); + void UnregisterMe(); + void RegisterMe(); }; } } diff --git a/xbmc/interfaces/python/PythonSwig.cpp.template b/xbmc/interfaces/python/PythonSwig.cpp.template index d3c106a8a9..67efb4e1b5 100644 --- a/xbmc/interfaces/python/PythonSwig.cpp.template +++ b/xbmc/interfaces/python/PythonSwig.cpp.template @@ -180,7 +180,7 @@ void doMethod(method, MethodType methodType) } // now do the method call itself if (!destructor) { - if (constructor || !clazz) { %> XBMCAddon::SetLanguageHookGuard slhg(XBMCAddon::Python::LanguageHook::getInstance());<% println() } + if (constructor || !clazz) { %> XBMCAddon::SetLanguageHookGuard slhg(XBMCAddon::Python::LanguageHook::GetIfExists(PyThreadState_Get()->interp).get());<% println() } %> <% if (returns != "void") { %>apiResult = (${SwigTypeParser.SwigType_lstr(returns)})<% } if (clazz && !constructor) { diff --git a/xbmc/interfaces/python/XBPyThread.cpp b/xbmc/interfaces/python/XBPyThread.cpp index 1ffad6d49a..82ca80f782 100644 --- a/xbmc/interfaces/python/XBPyThread.cpp +++ b/xbmc/interfaces/python/XBPyThread.cpp @@ -42,6 +42,7 @@ #include "XBPyThread.h" #include "XBPython.h" +#include "LanguageHook.h" #include "interfaces/legacy/Exception.h" #include "interfaces/legacy/CallbackHandler.h" @@ -158,6 +159,9 @@ void XBPyThread::Process() // swap in my thread state PyThreadState_Swap(state); + XBMCAddon::AddonClass::Ref<XBMCAddon::Python::LanguageHook> languageHook(new XBMCAddon::Python::LanguageHook(state->interp)); + languageHook->RegisterMe(); + m_pExecuter->InitializeInterpreter(addon); CLog::Log(LOGDEBUG, "%s - The source file to load is %s", __FUNCTION__, m_source); @@ -369,8 +373,56 @@ void XBPyThread::Process() m_pExecuter->DeInitializeInterpreter(); Py_EndInterpreter(state); - PyThreadState_Swap(NULL); + // This is a total hack. Python doesn't necessarily release + // all of the objects associated with the interpreter when + // you end the interpreter. As a result there are objects + // managed by the windowing system that still receive events + // until python decides to clean them up. Python will eventually + // clean them up on the creation or ending of a subsequent + // interpreter. So we are going to keep creating and ending + // interpreters until we have no more python objects hanging + // around. + int countLimit; + for (countLimit = 0; languageHook->HasRegisteredAddonClasses() && countLimit < 10; countLimit++) + { + PyThreadState* tmpstate = Py_NewInterpreter(); + Py_EndInterpreter(tmpstate); + } + + // If necessary and successfull, debug log the results. + if (countLimit > 0 && !languageHook->HasRegisteredAddonClasses()) + CLog::Log(LOGDEBUG,"It took %d Py_NewInterpreter/Py_EndInterpreter calls" + " to clean up the classes leftover from running \"%s.\"", + countLimit,m_source); + + // If not successful, produce an error message detailing what's been left behind + if (languageHook->HasRegisteredAddonClasses()) + { + CStdString message; + message.Format("The python script \"%s\" has left several " + "classes in memory that should have been cleaned up. The classes include: ", + m_source); + + { XBMCAddon::AddonClass::Synchronize l(*(languageHook.get())); + std::set<XBMCAddon::AddonClass*>& acs = languageHook->GetRegisteredAddonClasses(); + bool firstTime = true; + for (std::set<XBMCAddon::AddonClass*>::iterator iter = acs.begin(); + iter != acs.end(); iter++) + { + if (!firstTime) message += ","; + else firstTime = false; + message += (*iter)->GetClassname().c_str(); + } + } + + CLog::Log(LOGERROR, "%s", message.c_str()); + } + + // unregister the language hook + languageHook->UnregisterMe(); + + PyThreadState_Swap(NULL); PyEval_ReleaseLock(); } diff --git a/xbmc/interfaces/python/swig.cpp b/xbmc/interfaces/python/swig.cpp index d8840692c6..2a7475f160 100644 --- a/xbmc/interfaces/python/swig.cpp +++ b/xbmc/interfaces/python/swig.cpp @@ -19,7 +19,9 @@ * */ -#include "interfaces/python/swig.h" +#include "LanguageHook.h" +#include "swig.h" + #include <string> namespace PythonBindings @@ -202,7 +204,8 @@ namespace PythonBindings const char* methodNamespacePrefix, const char* methodNameForErrorString) throw (XBMCAddon::WrongTypeException) { if (pythonType == NULL || pythonType->magicNumber != XBMC_PYTHON_TYPE_MAGIC_NUMBER) - throw XBMCAddon::WrongTypeException("Non api type passed in place of the expected type \"%s.\"",expectedType); + throw XBMCAddon::WrongTypeException("Non api type passed to \"%s\" in place of the expected type \"%s.\"", + methodNameForErrorString, expectedType); if (!isParameterRightType(typeInfo->swigType,expectedType,methodNamespacePrefix)) { // maybe it's a child class @@ -216,5 +219,85 @@ namespace PythonBindings return ((PyHolder*)pythonType)->pSelf; } + /** + * This method is a helper for the generated API. It's called prior to any API + * class constructor being returned from the generated code to Python + */ + void prepareForReturn(XBMCAddon::AddonClass* c) + { + TRACE; + if(c) { + c->Acquire(); + PyThreadState* state = PyThreadState_Get(); + XBMCAddon::Python::LanguageHook::GetIfExists(state->interp)->RegisterAddonClassInstance(c); + } + } + + static bool handleInterpRegistrationForClean(XBMCAddon::AddonClass* c) + { + if(c){ + PyThreadState* state = PyThreadState_Get(); + XBMCAddon::AddonClass::Ref<XBMCAddon::Python::LanguageHook> lh = + XBMCAddon::Python::LanguageHook::GetIfExists(state->interp); + if (lh.isNotNull()) lh->UnregisterAddonClassInstance(c); + return true; + } + return false; + } + + /** + * This method is a helper for the generated API. It's called prior to any API + * class destructor being dealloc-ed from the generated code from Python + */ + void cleanForDealloc(XBMCAddon::AddonClass* c) + { + TRACE; + if (handleInterpRegistrationForClean(c)) + c->Release(); + } + + /** + * This method is a helper for the generated API. It's called prior to any API + * class destructor being dealloc-ed from the generated code from Python + * + * There is a Catch-22 in the destruction of a Window. 'dispose' needs to be + * called on destruction but cannot be called from the destructor. + * This overrides the default cleanForDealloc to resolve that. + */ + void cleanForDealloc(XBMCAddon::xbmcgui::Window* c) + { + TRACE; + if (handleInterpRegistrationForClean(c)) + { + c->dispose(); + c->Release(); + } + } + + /** + * This method allows for conversion of the native api Type to the Python type + * + * NOTE: swigTypeString must be in the data segment. That is, it should be an explicit string since + * the const char* is stored in a PyHolder struct and never deleted. + */ + PyObject* makePythonInstance(void* api, PyTypeObject* typeObj, TypeInfo* typeInfo, bool incrementRefCount) + { + // null api types result in Py_None + if (!api) + { + Py_INCREF(Py_None); + return Py_None; + } + + PyHolder* self = (PyHolder*)typeObj->tp_alloc(typeObj,0); + if (!self) return NULL; + self->magicNumber = XBMC_PYTHON_TYPE_MAGIC_NUMBER; + self->typeInfo = typeInfo; + self->pSelf = api; + if (incrementRefCount) + Py_INCREF((PyObject*)self); + return (PyObject*)self; + } + } diff --git a/xbmc/interfaces/python/swig.h b/xbmc/interfaces/python/swig.h index 22f899cbd6..c47f2af51f 100644 --- a/xbmc/interfaces/python/swig.h +++ b/xbmc/interfaces/python/swig.h @@ -104,14 +104,27 @@ namespace PythonBindings doretrieveApiInstance(((PyHolder*)pythonType),((PyHolder*)pythonType)->typeInfo, expectedType, methodNamespacePrefix, methodNameForErrorString); } - inline void prepareForReturn(XBMCAddon::AddonClass* c) { if(c) c->Acquire(); } + /** + * This method is a helper for the generated API. It's called prior to any API + * class constructor being returned from the generated code to Python + */ + void prepareForReturn(XBMCAddon::AddonClass* c); - inline void cleanForDealloc(XBMCAddon::AddonClass* c) { if(c) c->Release(); } + /** + * This method is a helper for the generated API. It's called prior to any API + * class destructor being dealloc-ed from the generated code from Python + */ + void cleanForDealloc(XBMCAddon::AddonClass* c); /** - * There is a Catch-22 in the destruction of a Window. This resolves that. + * This method is a helper for the generated API. It's called prior to any API + * class destructor being dealloc-ed from the generated code from Python + * + * There is a Catch-22 in the destruction of a Window. 'dispose' needs to be + * called on destruction but cannot be called from the destructor. + * This overrides the default cleanForDealloc to resolve that. */ - inline void cleanForDealloc(XBMCAddon::xbmcgui::Window* c) { if(c) { c->dispose(); c->Release(); } } + void cleanForDealloc(XBMCAddon::xbmcgui::Window* c); /** * This method allows for conversion of the native api Type to the Python type @@ -119,24 +132,7 @@ namespace PythonBindings * NOTE: swigTypeString must be in the data segment. That is, it should be an explicit string since * the const char* is stored in a PyHolder struct and never deleted. */ - inline PyObject* makePythonInstance(void* api, PyTypeObject* typeObj, TypeInfo* typeInfo, bool incrementRefCount) - { - // null api types result in Py_None - if (!api) - { - Py_INCREF(Py_None); - return Py_None; - } - - PyHolder* self = (PyHolder*)typeObj->tp_alloc(typeObj,0); - if (!self) return NULL; - self->magicNumber = XBMC_PYTHON_TYPE_MAGIC_NUMBER; - self->typeInfo = typeInfo; - self->pSelf = api; - if (incrementRefCount) - Py_INCREF((PyObject*)self); - return (PyObject*)self; - } + PyObject* makePythonInstance(void* api, PyTypeObject* typeObj, TypeInfo* typeInfo, bool incrementRefCount); class Director { |