Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CPythonInvoker: code cleanup #22412

Merged
merged 1 commit into from Feb 27, 2023

Conversation

repojohnray
Copy link
Contributor

Description

This commit fixes the memory leaks and updates initializeModule(). The function initializeModule() was allocating the python objects which were immediately leaked. The proper python initialization of these modules is done by PyImport_AppendInittab().

Motivation and context

The goal is to clean up some issues detected by the sanitizer.

How has this been tested?

The option "-fsanitize=leak" is required and should be passed to the compiler and the linker. For a fully sanitized executable, you could use the following options: "-fsanitize=bounds -fsanitize=address -fsanitize=undefined -fsanitize=unreachable -fsanitize=bool -fsanitize=enum -fsanitize=leak -fsanitize-recover=address"

An example below, or you could use the cmake option: -DECM_ENABLE_SANITIZERS
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS_DEBUG="-ggdb -O2 -fno-omit-frame-pointer -fsanitize=leak" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=leak" -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=leak"

The gcc sanitizer operates properly with a debug version of Python-3.11 compiled with the following options: '--without-pymalloc --with-pydebug --with-valgrind'. As a reminder with Python-3.8 the same options were not enough to generate a "sanitized" compatible python library.

What is the effect on users?

Screenshots (if appropriate):

Types of change

  • Bug fix (non-breaking change which fixes an issue)
  • Clean up (non-breaking change which removes non-working, unmaintained functionality)
  • Improvement (non-breaking change which improves existing functionality)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that will cause existing functionality to change)
  • Cosmetic change (non-breaking change that doesn't touch code)
  • None of the above (please explain below)

Checklist:

  • My code follows the Code Guidelines of this project
  • My change requires a change to the documentation, either Doxygen or wiki
  • I have updated the documentation accordingly
  • I have read the Contributing document
  • I have added tests to cover my change
  • All new and existing tests passed


if (Py_IsInitialized())
{
PyThreadState_Swap(PyInterpreterState_ThreadHead(PyInterpreterState_Main()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyInterpreterState_Main() requires Python >= 3.7. Is that dependency acceptable?

Test build on openSUSE Leap 15.4 with Python 3.6 is failing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right this function was already implemented (PythonInvoker.cpp) but with a version check. I will update the code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, is compiling and starting (no further tests yet).

{
for (const auto& module : modules)
for (int i = 0; i < numElements; i++)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason for removing the range for loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This construct operates locally and can't be used to transfer the size of the array to the base class.

{
if (module == NULL)
return false;

return module() != nullptr;
return !PyImport_AppendInittab(name, module);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While looking at #22344 I've noticed that inittab is a global resource in Python. Each module should only be added once. Modifying the inittab later may even crash.

My current proof proof concept (without this PR) is:

From dc07e8b90821896b4d529fb6abb57d04e7fac88f Mon Sep 17 00:00:00 2001
From: mglae <mglmail@arcor.de>
Date: Thu, 5 Jan 2023 00:35:22 +0100
Subject: [PATCH] AddonPythonInvoker: Only call PyImport_AppendInittab() once

The modules are added to a single Inittab so
a) The tab will grow with any call and duplicate entries are added.
b) The tab check code may crash while the tab is expanded.
---
 xbmc/interfaces/python/AddonPythonInvoker.cpp | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/xbmc/interfaces/python/AddonPythonInvoker.cpp b/xbmc/interfaces/python/AddonPythonInvoker.cpp
index b6158a8db6..34280f48ac 100644
--- a/xbmc/interfaces/python/AddonPythonInvoker.cpp
+++ b/xbmc/interfaces/python/AddonPythonInvoker.cpp
@@ -103,12 +103,18 @@ static PythonModule PythonModules[] =
 CAddonPythonInvoker::CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler)
   : CPythonInvoker(invocationHandler)
 {
-  PyImport_AppendInittab("xbmcdrm", PyInit_Module_xbmcdrm);
-  PyImport_AppendInittab("xbmcgui", PyInit_Module_xbmcgui);
-  PyImport_AppendInittab("xbmc", PyInit_Module_xbmc);
-  PyImport_AppendInittab("xbmcplugin", PyInit_Module_xbmcplugin);
-  PyImport_AppendInittab("xbmcaddon", PyInit_Module_xbmcaddon);
-  PyImport_AppendInittab("xbmcvfs", PyInit_Module_xbmcvfs);
+  static bool first_init(true);
+
+  if (first_init)
+  {
+    first_init = false;
+    PyImport_AppendInittab("xbmcdrm", PyInit_Module_xbmcdrm);
+    PyImport_AppendInittab("xbmcgui", PyInit_Module_xbmcgui);
+    PyImport_AppendInittab("xbmc", PyInit_Module_xbmc);
+    PyImport_AppendInittab("xbmcplugin", PyInit_Module_xbmcplugin);
+    PyImport_AppendInittab("xbmcaddon", PyInit_Module_xbmcaddon);
+    PyImport_AppendInittab("xbmcvfs", PyInit_Module_xbmcvfs);
+  }
 }
 
 CAddonPythonInvoker::~CAddonPythonInvoker() = default;
-- 
2.35.3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current code seems to implement a kind of "sub interpreter". This feature is not properly documented, and requires the function PyImport_AppendInittab() to be called locally. This new implementation doesn't change the previous behavior.

Here is an implementation of a "sub interpreter": https://gist.github.com/sterin/61561c3139dd49da1f43

It could be relevant to check #22344 with the gcc sanitizer (kodi + the python library).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current code seems to implement a kind of "sub interpreter". This feature is not properly documented, and requires the function PyImport_AppendInittab() to be called locally.

PyImport_AppendInittab() modifies a global table and should be called before Py_Initialize(), see comment and implementation here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyImport_AppendInittab() modifies a global table and should be called before Py_Initialize(), see comment and implementation here.

I tried, this is described in the python documentation, but the "sub interpreter" was not working anymore. This new implementation would log the PyImport_AppendInittab() errors which are not happening.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The #22344 issue seems to be related to thread safety, I doubt changing PyImport_AppendInittab() would fix anything, but the gcc sanitizer could help to find where the issue is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#22344 is a Python regression and will be fixed in the next 3.10/3.11 dot releases.

But while testing I noticed the PyImport_AppendInittab() related crashes.

The following patch to this PR is working fine in my Linux builds with external Python 3.6.15 and 3.11.1:

diff --git a/xbmc/interfaces/python/AddonPythonInvoker.cpp b/xbmc/interfaces/python/AddonPythonInvoker.cpp
index ca6b732af3..ce36309513 100644
--- a/xbmc/interfaces/python/AddonPythonInvoker.cpp
+++ b/xbmc/interfaces/python/AddonPythonInvoker.cpp
@@ -103,7 +103,13 @@ const std::array<PythonModule, 6> PythonModules =
 CAddonPythonInvoker::CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler)
   : CPythonInvoker(invocationHandler)
 {
-  initializeModules(PythonModules);
+  static bool first_init(true);
+
+  if (first_init)
+  {
+    first_init = false;
+    initializeModules(PythonModules);
+  }
 }
 
 CAddonPythonInvoker::~CAddonPythonInvoker() = default;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mglae, your patch is working, but we should keep in mind the official python documentation PyImport_AppendInittab(): - "This should be called before Py_Initialize()" https://docs.python.org/3/c-api/import.html

Here is a patch compliant with the python documentation:

diff --git a/xbmc/interfaces/python/AddonPythonInvoker.cpp b/xbmc/interfaces/python/AddonPythonInvoker.cpp
index ca6b732af3..b360c7d45d 100644
--- a/xbmc/interfaces/python/AddonPythonInvoker.cpp
+++ b/xbmc/interfaces/python/AddonPythonInvoker.cpp
@@ -103,11 +103,15 @@ const std::array<PythonModule, 6> PythonModules =
 CAddonPythonInvoker::CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler)
   : CPythonInvoker(invocationHandler)
 {
-  initializeModules(PythonModules);
 }
 
 CAddonPythonInvoker::~CAddonPythonInvoker() = default;
 
+void CAddonPythonInvoker::globalInitializeModules(void)
+{
+  initializeModules(PythonModules);
+}
+
 const char* CAddonPythonInvoker::getInitializationScript() const
 {
   return RUNSCRIPT_COMPLIANT;
diff --git a/xbmc/interfaces/python/AddonPythonInvoker.h b/xbmc/interfaces/python/AddonPythonInvoker.h
index 5ba0786438..cacf144ddd 100644
--- a/xbmc/interfaces/python/AddonPythonInvoker.h
+++ b/xbmc/interfaces/python/AddonPythonInvoker.h
@@ -16,6 +16,8 @@ public:
   explicit CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler);
   ~CAddonPythonInvoker() override;
 
+  static void globalInitializeModules(void);
+
 protected:
   // overrides of CPythonInvoker
   const char* getInitializationScript() const override;
diff --git a/xbmc/interfaces/python/PythonInvoker.h b/xbmc/interfaces/python/PythonInvoker.h
index 4583e1629a..09b68c2f0e 100644
--- a/xbmc/interfaces/python/PythonInvoker.h
+++ b/xbmc/interfaces/python/PythonInvoker.h
@@ -60,18 +60,18 @@ protected:
 
 protected:
   template<size_t NUMBER>
-  void initializeModules(const std::array<PythonModule, NUMBER> modules)
+  static void initializeModules(const std::array<PythonModule, NUMBER> modules)
   {
     for (const auto& module : modules)
     {
       if (!initializeModule(module.name, module.initialization))
-        CLog::Log(LOGWARNING, "CPythonInvoker({}, {}): unable to initialize python module \"{}\"",
-                  GetId(), m_sourceFile, module.name);
+        CLog::Log(LOGWARNING, "CPythonInvoker(): unable to initialize python module \"{}\"",
+                  module.name);
     }
   }
 
 private:
-  bool initializeModule(const char* name, PythonModuleInitialization module);
+  static bool initializeModule(const char* name, PythonModuleInitialization module);
   void getAddonModuleDeps(const ADDON::AddonPtr& addon, std::set<std::string>& paths);
   bool execute(const std::string& script, std::vector<std::wstring>& arguments);
   FILE* PyFile_AsFileWithMode(PyObject* py_file, const char* mode);
diff --git a/xbmc/interfaces/python/XBPython.cpp b/xbmc/interfaces/python/XBPython.cpp
index 9cb9ba00ac..86079d1d28 100644
--- a/xbmc/interfaces/python/XBPython.cpp
+++ b/xbmc/interfaces/python/XBPython.cpp
@@ -34,6 +34,10 @@
 #include "platform/Environment.h"
 #endif
 
+#ifdef HAS_WEB_INTERFACE
+#include "network/httprequesthandler/python/HTTPPythonWsgiInvoker.h"
+#endif
+
 #include <algorithm>
 
 // Only required for Py3 < 3.7
@@ -526,6 +530,12 @@ bool XBPython::OnScriptInitialized(ILanguageInvoker* invoker)
     Py_OptimizeFlag = 1;
 #endif
 
+    CAddonPythonInvoker::globalInitializeModules();
+
+#ifdef HAS_WEB_INTERFACE
+    CHTTPPythonWsgiInvoker::globalInitializeModules();
+#endif
+
     Py_Initialize();
 
 #if PY_VERSION_HEX < 0x03070000
diff --git a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
index 3bc46c1b76..1dd3f2d33a 100644
--- a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
+++ b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
@@ -92,7 +92,6 @@ CHTTPPythonWsgiInvoker::CHTTPPythonWsgiInvoker(ILanguageInvocationHandler* invoc
   : CHTTPPythonInvoker(invocationHandler, request),
     m_wsgiResponse(NULL)
 {
-  initializeModules(PythonModules);
 }
 
 CHTTPPythonWsgiInvoker::~CHTTPPythonWsgiInvoker()
@@ -101,6 +100,11 @@ CHTTPPythonWsgiInvoker::~CHTTPPythonWsgiInvoker()
   m_wsgiResponse = NULL;
 }
 
+void CHTTPPythonWsgiInvoker::globalInitializeModules(void)
+{
+  initializeModules(PythonModules);
+}
+
 HTTPPythonRequest* CHTTPPythonWsgiInvoker::GetRequest()
 {
   if (m_request == NULL || m_wsgiResponse == NULL)
diff --git a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.h b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.h
index 73f9ccf0be..86f075e517 100644
--- a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.h
+++ b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.h
@@ -29,6 +29,8 @@ public:
   CHTTPPythonWsgiInvoker(ILanguageInvocationHandler* invocationHandler, HTTPPythonRequest* request);
   ~CHTTPPythonWsgiInvoker() override;
 
+  static void globalInitializeModules(void);
+
   // implementations of CHTTPPythonInvoker
   HTTPPythonRequest* GetRequest() override;
 

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@repojohnray Agree, this is the correct way and it's working fine.

I can confirm this commit fixes the memory leaks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mglae, I will update the commit.

@repojohnray
Copy link
Contributor Author

The commit is updated.

@neo1973
Copy link
Member

neo1973 commented Jan 7, 2023

I suggest to convert the c-style arrays to std::array:

diff --git a/xbmc/interfaces/python/AddonPythonInvoker.cpp b/xbmc/interfaces/python/AddonPythonInvoker.cpp
index 399593b81b..ca6b732af3 100644
--- a/xbmc/interfaces/python/AddonPythonInvoker.cpp
+++ b/xbmc/interfaces/python/AddonPythonInvoker.cpp
@@ -9,6 +9,7 @@
 // python.h should always be included first before any other includes
 #include "AddonPythonInvoker.h"
 
+#include <array>
 #include <utility>
 
 #include <Python.h>
@@ -87,14 +88,14 @@ using namespace PythonBindings;
 namespace
 {
 // clang-format off
-const PythonModule PythonModules[] =
+const std::array<PythonModule, 6> PythonModules =
   {
-    { "xbmcdrm",    PyInit_Module_xbmcdrm    },
-    { "xbmcgui",    PyInit_Module_xbmcgui    },
-    { "xbmc",       PyInit_Module_xbmc       },
-    { "xbmcplugin", PyInit_Module_xbmcplugin },
-    { "xbmcaddon",  PyInit_Module_xbmcaddon  },
-    { "xbmcvfs",    PyInit_Module_xbmcvfs    }
+    PythonModule{ "xbmcdrm",    PyInit_Module_xbmcdrm    },
+    PythonModule{ "xbmcgui",    PyInit_Module_xbmcgui    },
+    PythonModule{ "xbmc",       PyInit_Module_xbmc       },
+    PythonModule{ "xbmcplugin", PyInit_Module_xbmcplugin },
+    PythonModule{ "xbmcaddon",  PyInit_Module_xbmcaddon  },
+    PythonModule{ "xbmcvfs",    PyInit_Module_xbmcvfs    }
   };
 // clang-format on
 } // namespace
@@ -102,7 +103,7 @@ const PythonModule PythonModules[] =
 CAddonPythonInvoker::CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler)
   : CPythonInvoker(invocationHandler)
 {
-  initializeModules(PythonModules, sizeof(PythonModules) / sizeof(*PythonModules));
+  initializeModules(PythonModules);
 }
 
 CAddonPythonInvoker::~CAddonPythonInvoker() = default;
diff --git a/xbmc/interfaces/python/PythonInvoker.cpp b/xbmc/interfaces/python/PythonInvoker.cpp
index 7e57ad92b7..32b9089d8d 100644
--- a/xbmc/interfaces/python/PythonInvoker.cpp
+++ b/xbmc/interfaces/python/PythonInvoker.cpp
@@ -678,16 +678,6 @@ void CPythonInvoker::onError(const std::string& exceptionType /* = "" */,
   }
 }
 
-void CPythonInvoker::initializeModules(const PythonModule modules[], const int numElements)
-{
-  for (int i = 0; i < numElements; i++)
-  {
-    if (!initializeModule(modules[i].name, modules[i].initialization))
-      CLog::Log(LOGWARNING, "CPythonInvoker({}, {}): unable to initialize python module \"{}\"",
-                GetId(), m_sourceFile, modules[i].name);
-  }
-}
-
 bool CPythonInvoker::initializeModule(const char* name, PythonModuleInitialization module)
 {
   if (module == NULL)
diff --git a/xbmc/interfaces/python/PythonInvoker.h b/xbmc/interfaces/python/PythonInvoker.h
index 3018d1bf82..4583e1629a 100644
--- a/xbmc/interfaces/python/PythonInvoker.h
+++ b/xbmc/interfaces/python/PythonInvoker.h
@@ -59,7 +59,16 @@ protected:
   CCriticalSection m_critical;
 
 protected:
-  void initializeModules(const PythonModule modules[], const int numElements);
+  template<size_t NUMBER>
+  void initializeModules(const std::array<PythonModule, NUMBER> modules)
+  {
+    for (const auto& module : modules)
+    {
+      if (!initializeModule(module.name, module.initialization))
+        CLog::Log(LOGWARNING, "CPythonInvoker({}, {}): unable to initialize python module \"{}\"",
+                  GetId(), m_sourceFile, module.name);
+    }
+  }
 
 private:
   bool initializeModule(const char* name, PythonModuleInitialization module);
diff --git a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
index c617ba54e9..3bc46c1b76 100644
--- a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
+++ b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
@@ -19,6 +19,7 @@
 #include "utils/StringUtils.h"
 #include "utils/URIUtils.h"
 
+#include <array>
 #include <utility>
 
 #include <Python.h>
@@ -78,11 +79,11 @@ using namespace PythonBindings;
 namespace
 {
 // clang-format off
-const PythonModule PythonModules[] =
+const std::array<PythonModule, 3> PythonModules =
 {
-  { "xbmc",           PyInit_Module_xbmc },
-  { "xbmcaddon",      PyInit_Module_xbmcaddon },
-  { "xbmcwsgi",       PyInit_Module_xbmcwsgi }
+  PythonModule{ "xbmc",           PyInit_Module_xbmc },
+  PythonModule{ "xbmcaddon",      PyInit_Module_xbmcaddon },
+  PythonModule{ "xbmcwsgi",       PyInit_Module_xbmcwsgi }
 };
 // clang-format on
 } // namespace
@@ -91,7 +92,7 @@ CHTTPPythonWsgiInvoker::CHTTPPythonWsgiInvoker(ILanguageInvocationHandler* invoc
   : CHTTPPythonInvoker(invocationHandler, request),
     m_wsgiResponse(NULL)
 {
-  initializeModules(PythonModules, sizeof(PythonModules) / sizeof(*PythonModules));
+  initializeModules(PythonModules);
 }
 
 CHTTPPythonWsgiInvoker::~CHTTPPythonWsgiInvoker()

Other than that I don't experience issues at runtime and it definitely reduces the number of leaks on exit 😃.

@repojohnray
Copy link
Contributor Author

@neo1973, this is not a good idea, PyImport_AppendInittab() requires a valid name until the end of the session (c_str() like). This was previously implemented with a direct call to PyImport_AppendInittab() at the constructor level; Some dead code was also using "static std::map<std::string, PythonModuleInitialization> modules;" a solution that is not thread safe. The current code lets the linker processes everything, this is the fastest and optimal solution.

@neo1973
Copy link
Member

neo1973 commented Jan 7, 2023

@repojohnray: Sorry, I meant to pass the std::arrays as const references, not by value, if that was your concern. This is just as fast and definitely cleaner.

@repojohnray
Copy link
Contributor Author

repojohnray commented Jan 7, 2023

The c_str() reference of the std::string (if we want to use std::string with the std::array) would likely be deleted before the end of the session, this would crash the python "sub interpreter". The main issue here is not to use any dynamically allocated object.

@neo1973
Copy link
Member

neo1973 commented Jan 7, 2023

I don't want to change to code to use std::string, I just want to get rid of the c-arrays and the ugly length calculations it requires. Also being able to use ranged for loops is nicer.

@repojohnray
Copy link
Contributor Author

@neo1973, OK, I check with the sanitizer.

@repojohnray
Copy link
Contributor Author

@neo1973, your patch is working flawlessly. I update the commit.

@wsnipex wsnipex added this to the Omega 21.0 Alpha 1 milestone Jan 8, 2023
@wsnipex wsnipex added Type: Fix non-breaking change which fixes an issue Component: Python v21 Omega labels Jan 8, 2023
@repojohnray
Copy link
Contributor Author

The commit is updated to call PyImport_AppendInittab() before Py_Initialize() as required by the Python documentation.

Copy link
Contributor

@lrusak lrusak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I added a few comments mostly about style.

{ "xbmc", PyInit_Module_xbmc },
{ "xbmcaddon", PyInit_Module_xbmcaddon },
{ "xbmcwsgi", PyInit_Module_xbmcwsgi }
PythonModule{ "xbmc", PyInit_Module_xbmc },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicit type shouldn't be needed

Suggested change
PythonModule{ "xbmc", PyInit_Module_xbmc },
{ "xbmc", PyInit_Module_xbmc },

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last patch by @neo1973 simplifies this issue.

@@ -29,13 +29,14 @@ class CHTTPPythonWsgiInvoker : public CHTTPPythonInvoker
CHTTPPythonWsgiInvoker(ILanguageInvocationHandler* invocationHandler, HTTPPythonRequest* request);
~CHTTPPythonWsgiInvoker() override;

static void globalInitializeModules(void);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static void globalInitializeModules(void);
static void GlobalInitializeModules();

ref: https://github.com/xbmc/xbmc/blob/master/docs/CODE_GUIDELINES.md#86-methods
and: https://github.com/xbmc/xbmc/blob/master/docs/CODE_GUIDELINES.md#36-superfluous-void

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lrusak, you are right this is updated in the last commit.

@@ -16,8 +16,9 @@ class CAddonPythonInvoker : public CPythonInvoker
explicit CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler);
~CAddonPythonInvoker() override;

static void globalInitializeModules(void);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static void globalInitializeModules(void);
static void GlobalInitializeModules();

ref: https://github.com/xbmc/xbmc/blob/master/docs/CODE_GUIDELINES.md#86-methods
and: https://github.com/xbmc/xbmc/blob/master/docs/CODE_GUIDELINES.md#36-superfluous-void

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lrusak, this is updated in the last commit.

@@ -58,9 +58,20 @@ class CPythonInvoker : public ILanguageInvoker
std::string m_sourceFile;
CCriticalSection m_critical;

protected:
template<size_t NUMBER>
static void initializeModules(const std::array<PythonModule, NUMBER> modules)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static void initializeModules(const std::array<PythonModule, NUMBER> modules)
static void InitializeModules(const std::array<PythonModule, NUMBER>& modules)

ref: https://github.com/xbmc/xbmc/blob/master/docs/CODE_GUIDELINES.md#86-methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is removed.

@@ -58,9 +58,20 @@ class CPythonInvoker : public ILanguageInvoker
std::string m_sourceFile;
CCriticalSection m_critical;

protected:
template<size_t NUMBER>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NUMBER isn't a great name, SIZE or COUNT would be more suitable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is removed.


struct PythonModule
{
const char* name;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use std::string_view instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyImport_AppendInittab() and similar functions don't copy the module name. Using a non-dynamically allocated object like "const char *" is safer.

{ "xbmcplugin", PyInit_Module_xbmcplugin },
{ "xbmcaddon", PyInit_Module_xbmcaddon },
{ "xbmcvfs", PyInit_Module_xbmcvfs }
PythonModule{ "xbmcdrm", PyInit_Module_xbmcdrm },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't use whitespace formatting, you can leave it to clang-format unless there really is a readability issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

PyObject* pyaddonid = PyUnicode_FromString(m_addon->ID().c_str());
PyDict_SetItemString(moduleDictionary, "__xbmcaddonid__", pyaddonid);
PyDict_SetItemString(moduleDictionary, "__xbmcaddonid__",
PyObjectPtr(PyUnicode_FromString(m_addon->ID().c_str())).get());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this could still be two lines, not a big deal though.

auto id = PyObjectPtr(PyUnicode_FromString(m_addon->ID().c_str()));
PyDict_SetItemString(moduleDictionary, "__xbmcaddonid__", id.get());

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This C++ syntax is not optimal, but it does the job.

@neo1973
Copy link
Member

neo1973 commented Jan 10, 2023

I just realized that this could be simplified quite a bit by using PyImport_ExtendInittab (doc), making the template method and some structs/typedefs obsolete:

Patch
diff --git a/xbmc/interfaces/python/AddonPythonInvoker.cpp b/xbmc/interfaces/python/AddonPythonInvoker.cpp
index b360c7d45d..7eef37c8ea 100644
--- a/xbmc/interfaces/python/AddonPythonInvoker.cpp
+++ b/xbmc/interfaces/python/AddonPythonInvoker.cpp
@@ -9,7 +9,6 @@
 // python.h should always be included first before any other includes
 #include "AddonPythonInvoker.h"
 
-#include <array>
 #include <utility>
 
 #include <Python.h>
@@ -88,14 +87,15 @@ using namespace PythonBindings;
 namespace
 {
 // clang-format off
-const std::array<PythonModule, 6> PythonModules =
+const _inittab PythonModules[] =
   {
-    PythonModule{ "xbmcdrm",    PyInit_Module_xbmcdrm    },
-    PythonModule{ "xbmcgui",    PyInit_Module_xbmcgui    },
-    PythonModule{ "xbmc",       PyInit_Module_xbmc       },
-    PythonModule{ "xbmcplugin", PyInit_Module_xbmcplugin },
-    PythonModule{ "xbmcaddon",  PyInit_Module_xbmcaddon  },
-    PythonModule{ "xbmcvfs",    PyInit_Module_xbmcvfs    }
+    { "xbmcdrm",    PyInit_Module_xbmcdrm    },
+    { "xbmcgui",    PyInit_Module_xbmcgui    },
+    { "xbmc",       PyInit_Module_xbmc       },
+    { "xbmcplugin", PyInit_Module_xbmcplugin },
+    { "xbmcaddon",  PyInit_Module_xbmcaddon  },
+    { "xbmcvfs",    PyInit_Module_xbmcvfs    },
+    { nullptr,      nullptr }
   };
 // clang-format on
 } // namespace
@@ -109,7 +109,8 @@ CAddonPythonInvoker::~CAddonPythonInvoker() = default;
 
 void CAddonPythonInvoker::globalInitializeModules(void)
 {
-  initializeModules(PythonModules);
+  if (PyImport_ExtendInittab(const_cast<_inittab*>(PythonModules)))
+    CLog::Log(LOGWARNING, "CAddonPythonInvoker(): unable to extend inittab");
 }
 
 const char* CAddonPythonInvoker::getInitializationScript() const
diff --git a/xbmc/interfaces/python/PythonInvoker.cpp b/xbmc/interfaces/python/PythonInvoker.cpp
index 32b9089d8d..8da7becdcf 100644
--- a/xbmc/interfaces/python/PythonInvoker.cpp
+++ b/xbmc/interfaces/python/PythonInvoker.cpp
@@ -678,14 +678,6 @@ void CPythonInvoker::onError(const std::string& exceptionType /* = "" */,
   }
 }
 
-bool CPythonInvoker::initializeModule(const char* name, PythonModuleInitialization module)
-{
-  if (module == NULL)
-    return false;
-
-  return !PyImport_AppendInittab(name, module);
-}
-
 void CPythonInvoker::getAddonModuleDeps(const ADDON::AddonPtr& addon, std::set<std::string>& paths)
 {
   for (const auto& it : addon->GetDependencies())
diff --git a/xbmc/interfaces/python/PythonInvoker.h b/xbmc/interfaces/python/PythonInvoker.h
index 09b68c2f0e..e4ad95e8b4 100644
--- a/xbmc/interfaces/python/PythonInvoker.h
+++ b/xbmc/interfaces/python/PythonInvoker.h
@@ -18,7 +18,6 @@
 #include <string>
 #include <vector>
 
-struct PythonModule;
 typedef struct _object PyObject;
 
 class CPythonInvoker : public ILanguageInvoker
@@ -32,8 +31,6 @@ public:
 
   bool IsStopping() const override { return m_stop || ILanguageInvoker::IsStopping(); }
 
-  typedef PyObject* (*PythonModuleInitialization)();
-
 protected:
   // implementation of ILanguageInvoker
   bool execute(const std::string& script, const std::vector<std::string>& arguments) override;
@@ -58,20 +55,7 @@ protected:
   std::string m_sourceFile;
   CCriticalSection m_critical;
 
-protected:
-  template<size_t NUMBER>
-  static void initializeModules(const std::array<PythonModule, NUMBER> modules)
-  {
-    for (const auto& module : modules)
-    {
-      if (!initializeModule(module.name, module.initialization))
-        CLog::Log(LOGWARNING, "CPythonInvoker(): unable to initialize python module \"{}\"",
-                  module.name);
-    }
-  }
-
 private:
-  static bool initializeModule(const char* name, PythonModuleInitialization module);
   void getAddonModuleDeps(const ADDON::AddonPtr& addon, std::set<std::string>& paths);
   bool execute(const std::string& script, std::vector<std::wstring>& arguments);
   FILE* PyFile_AsFileWithMode(PyObject* py_file, const char* mode);
@@ -94,9 +78,3 @@ private:
 public:
   typedef std::unique_ptr<PyObject, PyObjectDeleter> PyObjectPtr;
 };
-
-struct PythonModule
-{
-  const char* name;
-  CPythonInvoker::PythonModuleInitialization initialization;
-};
diff --git a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
index 1dd3f2d33a..81880bcf97 100644
--- a/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
+++ b/xbmc/network/httprequesthandler/python/HTTPPythonWsgiInvoker.cpp
@@ -19,7 +19,6 @@
 #include "utils/StringUtils.h"
 #include "utils/URIUtils.h"
 
-#include <array>
 #include <utility>
 
 #include <Python.h>
@@ -79,11 +78,12 @@ using namespace PythonBindings;
 namespace
 {
 // clang-format off
-const std::array<PythonModule, 3> PythonModules =
+const _inittab PythonModules[] =
 {
-  PythonModule{ "xbmc",           PyInit_Module_xbmc },
-  PythonModule{ "xbmcaddon",      PyInit_Module_xbmcaddon },
-  PythonModule{ "xbmcwsgi",       PyInit_Module_xbmcwsgi }
+  { "xbmc",           PyInit_Module_xbmc },
+  { "xbmcaddon",      PyInit_Module_xbmcaddon },
+  { "xbmcwsgi",       PyInit_Module_xbmcwsgi },
+  { nullptr,          nullptr }
 };
 // clang-format on
 } // namespace
@@ -102,7 +102,8 @@ CHTTPPythonWsgiInvoker::~CHTTPPythonWsgiInvoker()
 
 void CHTTPPythonWsgiInvoker::globalInitializeModules(void)
 {
-  initializeModules(PythonModules);
+  if (PyImport_ExtendInittab(const_cast<_inittab*>(PythonModules)))
+    CLog::Log(LOGWARNING, "CHTTPPythonWsgiInvoker(): unable to extend inittab");
 }
 
 HTTPPythonRequest* CHTTPPythonWsgiInvoker::GetRequest()

@repojohnray
Copy link
Contributor Author

@neo1973, the last commit is updated with your patch. Kodi is working flawlessly.

@repojohnray
Copy link
Contributor Author

This last change updates the comment.

@fuzzard fuzzard merged commit 23e7da3 into xbmc:master Feb 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Component: Python Type: Fix non-breaking change which fixes an issue v21 Omega
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants