]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/mgr/MgrPyModule.cc
update sources to v12.1.0
[ceph.git] / ceph / src / mgr / MgrPyModule.cc
index 078f7f503503f17bfae837265bd981c0f1791ae6..fda9bf6528dbd90500cad04bb4569d069b9f6be0 100644 (file)
@@ -11,6 +11,8 @@
  * Foundation.  See file COPYING.
  */
 
+#include "PyState.h"
+#include "Gil.h"
 
 #include "PyFormatter.h"
 
@@ -46,32 +48,121 @@ std::string handle_pyerror()
 
 #define dout_context g_ceph_context
 #define dout_subsys ceph_subsys_mgr
+
+#undef dout_prefix
+#define dout_prefix *_dout << "mgr[py] "
+
+namespace {
+  PyObject* log_write(PyObject*, PyObject* args) {
+    char* m = nullptr;
+    if (PyArg_ParseTuple(args, "s", &m)) {
+      auto len = strlen(m);
+      if (len && m[len-1] == '\n') {
+       m[len-1] = '\0';
+      }
+      dout(4) << m << dendl;
+    }
+    Py_RETURN_NONE;
+  }
+
+  PyObject* log_flush(PyObject*, PyObject*){
+    Py_RETURN_NONE;
+  }
+
+  static PyMethodDef log_methods[] = {
+    {"write", log_write, METH_VARARGS, "write stdout and stderr"},
+    {"flush", log_flush, METH_VARARGS, "flush"},
+    {nullptr, nullptr, 0, nullptr}
+  };
+}
+
 #undef dout_prefix
 #define dout_prefix *_dout << "mgr " << __func__ << " "
 
-MgrPyModule::MgrPyModule(const std::string &module_name_)
+MgrPyModule::MgrPyModule(const std::string &module_name_, const std::string &sys_path, PyThreadState *main_ts_)
   : module_name(module_name_),
-    pClassInstance(nullptr)
-{}
-
-MgrPyModule::~MgrPyModule()
+    pClassInstance(nullptr),
+    pMainThreadState(main_ts_)
 {
-  PyGILState_STATE gstate;
-  gstate = PyGILState_Ensure();
+  assert(pMainThreadState != nullptr);
+
+  Gil gil(pMainThreadState);
+
+  pMyThreadState = Py_NewInterpreter();
+  if (pMyThreadState == nullptr) {
+    derr << "Failed to create python sub-interpreter for '" << module_name << '"' << dendl;
+  } else {
+    // Some python modules do not cope with an unpopulated argv, so lets
+    // fake one.  This step also picks up site-packages into sys.path.
+    const char *argv[] = {"ceph-mgr"};
+    PySys_SetArgv(1, (char**)argv);
+
+    if (g_conf->daemonize) {
+      auto py_logger = Py_InitModule("ceph_logger", log_methods);
+#if PY_MAJOR_VERSION >= 3
+      PySys_SetObject("stderr", py_logger);
+      PySys_SetObject("stdout", py_logger);
+#else
+      PySys_SetObject(const_cast<char*>("stderr"), py_logger);
+      PySys_SetObject(const_cast<char*>("stdout"), py_logger);
+#endif
+    }
+    // Populate python namespace with callable hooks
+    Py_InitModule("ceph_state", CephStateMethods);
 
-  Py_XDECREF(pClassInstance);
+    PySys_SetPath(const_cast<char*>(sys_path.c_str()));
+  }
+}
 
-  PyGILState_Release(gstate);
+MgrPyModule::~MgrPyModule()
+{
+  if (pMyThreadState != nullptr) {
+    Gil gil(pMyThreadState);
+
+    Py_XDECREF(pClassInstance);
+
+    //
+    // Ideally, now, we'd be able to do this:
+    //
+    //    Py_EndInterpreter(pMyThreadState);
+    //    PyThreadState_Swap(pMainThreadState);
+    //
+    // Unfortunately, if the module has any other *python* threads active
+    // at this point, Py_EndInterpreter() will abort with:
+    //
+    //    Fatal Python error: Py_EndInterpreter: not the last thread
+    //
+    // This can happen when using CherryPy in a module, becuase CherryPy
+    // runs an extra thread as a timeout monitor, which spends most of its
+    // life inside a time.sleep(60).  Unless you are very, very lucky with
+    // the timing calling this destructor, that thread will still be stuck
+    // in a sleep, and Py_EndInterpreter() will abort.
+    //
+    // This could of course also happen with a poorly written module which
+    // made no attempt to clean up any additional threads it created.
+    //
+    // The safest thing to do is just not call Py_EndInterpreter(), and
+    // let Py_Finalize() kill everything after all modules are shut down.
+    //
+  }
 }
 
 int MgrPyModule::load()
 {
+  if (pMyThreadState == nullptr) {
+    derr << "No python sub-interpreter exists for module '" << module_name << "'" << dendl;
+    return -EINVAL;
+  }
+
+  Gil gil(pMyThreadState);
+
   // Load the module
   PyObject *pName = PyString_FromString(module_name.c_str());
   auto pModule = PyImport_Import(pName);
   Py_DECREF(pName);
   if (pModule == nullptr) {
     derr << "Module not found: '" << module_name << "'" << dendl;
+    derr << handle_pyerror() << dendl;
     return -ENOENT;
   }
 
@@ -81,6 +172,7 @@ int MgrPyModule::load()
   Py_DECREF(pModule);
   if (pClass == nullptr) {
     derr << "Class not found in module '" << module_name << "'" << dendl;
+    derr << handle_pyerror() << dendl;
     return -EINVAL;
   }
 
@@ -95,6 +187,7 @@ int MgrPyModule::load()
   Py_DECREF(pArgs);
   if (pClassInstance == nullptr) {
     derr << "Failed to construct class in '" << module_name << "'" << dendl;
+    derr << handle_pyerror() << dendl;
     return -EINVAL;
   } else {
     dout(1) << "Constructed class from module: " << module_name << dendl;
@@ -107,8 +200,9 @@ int MgrPyModule::serve()
 {
   assert(pClassInstance != nullptr);
 
-  PyGILState_STATE gstate;
-  gstate = PyGILState_Ensure();
+  // This method is called from a separate OS thread (i.e. a thread not
+  // created by Python), so tell Gil to wrap this in a new thread state.
+  Gil gil(pMyThreadState, true);
 
   auto pValue = PyObject_CallMethod(pClassInstance,
       const_cast<char*>("serve"), nullptr);
@@ -122,8 +216,6 @@ int MgrPyModule::serve()
     return -EINVAL;
   }
 
-  PyGILState_Release(gstate);
-
   return r;
 }
 
@@ -132,8 +224,7 @@ void MgrPyModule::shutdown()
 {
   assert(pClassInstance != nullptr);
 
-  PyGILState_STATE gstate;
-  gstate = PyGILState_Ensure();
+  Gil gil(pMyThreadState);
 
   auto pValue = PyObject_CallMethod(pClassInstance,
       const_cast<char*>("shutdown"), nullptr);
@@ -144,16 +235,13 @@ void MgrPyModule::shutdown()
     derr << "Failed to invoke shutdown() on " << module_name << dendl;
     derr << handle_pyerror() << dendl;
   }
-
-  PyGILState_Release(gstate);
 }
 
 void MgrPyModule::notify(const std::string &notify_type, const std::string &notify_id)
 {
   assert(pClassInstance != nullptr);
 
-  PyGILState_STATE gstate;
-  gstate = PyGILState_Ensure();
+  Gil gil(pMyThreadState);
 
   // Execute
   auto pValue = PyObject_CallMethod(pClassInstance,
@@ -170,16 +258,13 @@ void MgrPyModule::notify(const std::string &notify_type, const std::string &noti
     // a hook to unload misbehaving modules when they have an
     // error somewhere like this
   }
-
-  PyGILState_Release(gstate);
 }
 
 void MgrPyModule::notify_clog(const LogEntry &log_entry)
 {
   assert(pClassInstance != nullptr);
 
-  PyGILState_STATE gstate;
-  gstate = PyGILState_Ensure();
+  Gil gil(pMyThreadState);
 
   // Construct python-ized LogEntry
   PyFormatter f;
@@ -201,15 +286,12 @@ void MgrPyModule::notify_clog(const LogEntry &log_entry)
     // a hook to unload misbehaving modules when they have an
     // error somewhere like this
   }
-
-  PyGILState_Release(gstate);
 }
 
 int MgrPyModule::load_commands()
 {
-  PyGILState_STATE gstate;
-  gstate = PyGILState_Ensure();
-
+  // Don't need a Gil here -- this is called from MgrPyModule::load(),
+  // which already has one.
   PyObject *command_list = PyObject_GetAttrString(pClassInstance, "COMMANDS");
   assert(command_list != nullptr);
   const size_t list_size = PyList_Size(command_list);
@@ -239,8 +321,6 @@ int MgrPyModule::load_commands()
   }
   Py_DECREF(command_list);
 
-  PyGILState_Release(gstate);
-
   dout(10) << "loaded " << commands.size() << " commands" << dendl;
 
   return 0;
@@ -254,8 +334,7 @@ int MgrPyModule::handle_command(
   assert(ss != nullptr);
   assert(ds != nullptr);
 
-  PyGILState_STATE gstate;
-  gstate = PyGILState_Ensure();
+  Gil gil(pMyThreadState);
 
   PyFormatter f;
   cmdmap_dump(cmdmap, &f);
@@ -283,8 +362,6 @@ int MgrPyModule::handle_command(
     r = -EINVAL;
   }
 
-  PyGILState_Release(gstate);
-
   return r;
 }