]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr: cut down duplication between active+standby
authorJohn Spray <john.spray@redhat.com>
Fri, 6 Oct 2017 15:02:44 +0000 (11:02 -0400)
committerJohn Spray <john.spray@redhat.com>
Wed, 1 Nov 2017 12:20:21 +0000 (08:20 -0400)
...by using PyModuleRunner class from ActivePyModule too.

Signed-off-by: John Spray <john.spray@redhat.com>
src/CMakeLists.txt
src/mgr/ActivePyModule.cc
src/mgr/ActivePyModule.h
src/mgr/ActivePyModules.cc
src/mgr/ActivePyModules.h
src/mgr/BaseMgrModule.cc
src/mgr/PyModuleRegistry.cc
src/mgr/PyModuleRunner.cc [new file with mode: 0644]
src/mgr/PyModuleRunner.h [new file with mode: 0644]
src/mgr/StandbyPyModules.cc
src/mgr/StandbyPyModules.h

index 997137111d05295e9757b7332df7380b4f2d0bab..9c9fa76e2e2dbbf5177b75bcf42d2695c1fdd612 100644 (file)
@@ -715,6 +715,7 @@ if (WITH_MGR)
       mgr/ActivePyModules.cc
       mgr/StandbyPyModules.cc
       mgr/PyModuleRegistry.cc
+      mgr/PyModuleRunner.cc
       mgr/PyFormatter.cc
       mgr/PyOSDMap.cc
       mgr/BaseMgrModule.cc
index 6048dc707cf81c02462870d12ac0343019398ef0..3a6fe6a9eca24f52d8e825e131dabca0c82beab7 100644 (file)
@@ -51,60 +51,9 @@ std::string handle_pyerror()
     return extract<std::string>(formatted);
 }
 
-
-void *ServeThread::entry()
-{
-  // No need to acquire the GIL here; the module does it.
-  dout(4) << "Entering thread for " << mod->get_name() << dendl;
-  mod->serve();
-  return nullptr;
-}
-
-ActivePyModule::ActivePyModule(const std::string &module_name_,
-                               PyObject *pClass_,
-                               PyThreadState *my_ts_)
-  : module_name(module_name_),
-    pClass(pClass_),
-    pMyThreadState(my_ts_),
-    thread(this)
-{
-}
-
-ActivePyModule::~ActivePyModule()
-{
-  if (pMyThreadState.ts != 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 ActivePyModule::load(ActivePyModules *py_modules)
 {
+  assert(py_modules);
   Gil gil(pMyThreadState);
 
   // We tell the module how we name it, so that it can be consistent
@@ -129,46 +78,6 @@ int ActivePyModule::load(ActivePyModules *py_modules)
   return load_commands();
 }
 
-int ActivePyModule::serve()
-{
-  assert(pClassInstance != nullptr);
-
-  // 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);
-
-  int r = 0;
-  if (pValue != NULL) {
-    Py_DECREF(pValue);
-  } else {
-    derr << module_name << ".serve:" << dendl;
-    derr << handle_pyerror() << dendl;
-    return -EINVAL;
-  }
-
-  return r;
-}
-
-void ActivePyModule::shutdown()
-{
-  assert(pClassInstance != nullptr);
-
-  Gil gil(pMyThreadState);
-
-  auto pValue = PyObject_CallMethod(pClassInstance,
-      const_cast<char*>("shutdown"), nullptr);
-
-  if (pValue != NULL) {
-    Py_DECREF(pValue);
-  } else {
-    derr << "Failed to invoke shutdown() on " << module_name << dendl;
-    derr << handle_pyerror() << dendl;
-  }
-}
-
 void ActivePyModule::notify(const std::string &notify_type, const std::string &notify_id)
 {
   assert(pClassInstance != nullptr);
index 839f678f75297af369aaf8768b4e1f4b8a9a7b53..2a3e0c8bfe092d95614ca3f9acfc6e6336983a7b 100644 (file)
@@ -25,6 +25,8 @@
 #include "mon/health_check.h"
 #include "mgr/Gil.h"
 
+#include "PyModuleRunner.h"
+
 #include <vector>
 #include <string>
 
@@ -43,32 +45,9 @@ public:
   ActivePyModule *handler;
 };
 
-class ServeThread : public Thread
-{
-  ActivePyModule *mod;
-
-public:
-  ServeThread(ActivePyModule *mod_)
-    : mod(mod_) {}
-
-  void *entry() override;
-};
-
-class ActivePyModule
+class ActivePyModule : public PyModuleRunner
 {
 private:
-  const std::string module_name;
-
-  // Passed in by whoever loaded our python module and looked up
-  // the symbols in it.
-  PyObject *pClass = nullptr;
-
-  // Passed in by whoever created our subinterpreter for us
-  SafeThreadState pMyThreadState = nullptr;
-
-  // Populated when we construct our instance of pClass in load()
-  PyObject *pClassInstance = nullptr;
-
   health_check_map_t health_checks;
 
   std::vector<ModuleCommand> commands;
@@ -79,16 +58,13 @@ private:
   std::string uri;
 
 public:
-  ActivePyModule(const std::string &module_name,
+  ActivePyModule(const std::string &module_name_,
       PyObject *pClass_,
-      PyThreadState *my_ts);
-  ~ActivePyModule();
-
-  ServeThread thread;
+      PyThreadState *my_ts_)
+    : PyModuleRunner(module_name_, pClass_, my_ts_)
+  {}
 
   int load(ActivePyModules *py_modules);
-  int serve();
-  void shutdown();
   void notify(const std::string &notify_type, const std::string &notify_id);
   void notify_clog(const LogEntry &le);
 
@@ -97,11 +73,6 @@ public:
     return commands;
   }
 
-  const std::string &get_name() const
-  {
-    return module_name;
-  }
-
   int handle_command(
     const cmdmap_t &cmdmap,
     std::stringstream *ds,
index 3dcf65dffd161e126c8ff292f074cd026bd423be..e1cde4fd7d31d3dafd409b86a880779c565e695b 100644 (file)
@@ -35,8 +35,6 @@
 #undef dout_prefix
 #define dout_prefix *_dout << "mgr " << __func__ << " "
 
-// constructor/destructor implementations cannot be in .h,
-// because ServeThread is still an "incomplete" type there
 
 ActivePyModules::ActivePyModules(PyModuleConfig const &config_,
           DaemonStateIndex &ds, ClusterState &cs,
@@ -536,16 +534,6 @@ std::map<std::string, std::string> ActivePyModules::get_services() const
   return result;
 }
 
-void ActivePyModules::log(const std::string &module_name,
-    int level, const std::string &record)
-{
-#undef dout_prefix
-#define dout_prefix *_dout << "mgr[" << module_name << "] "
-  dout(level) << record << dendl;
-#undef dout_prefix
-#define dout_prefix *_dout << "mgr " << __func__ << " "
-}
-
 PyObject* ActivePyModules::get_counter_python(
     const std::string &svc_name,
     const std::string &svc_id,
index 6e97e244c1fcc5790365aa18aaa66775612192c5..688469f8c135e0ef47e51456a5bcacad4897d729 100644 (file)
@@ -27,7 +27,6 @@
 #include "DaemonState.h"
 #include "ClusterState.h"
 
-class ServeThread;
 class health_check_map_t;
 
 typedef std::map<std::string, std::string> PyModuleConfig;
@@ -91,9 +90,6 @@ public:
 
   void set_uri(const std::string& module_name, const std::string &uri);
 
-  void log(const std::string &module_name,
-           int level, const std::string &record);
-
   // Python command definitions, including callback
   std::vector<ModuleCommand> get_py_commands() const;
 
index b16cc296502773a7d9b76acc740f3e3abcc154dc..6f7252e86b1596fa8e6ed9182f83cb3f58d1ac69 100644 (file)
@@ -410,6 +410,7 @@ get_daemon_status(BaseMgrModule *self, PyObject *args)
 static PyObject*
 ceph_log(BaseMgrModule *self, PyObject *args)
 {
+
   int level = 0;
   char *record = nullptr;
   if (!PyArg_ParseTuple(args, "is:log", &level, &record)) {
@@ -418,7 +419,7 @@ ceph_log(BaseMgrModule *self, PyObject *args)
 
   assert(self->this_module);
 
-  self->py_modules->log(self->this_module->get_name(), level, record);
+  self->this_module->log(level, record);
 
   Py_RETURN_NONE;
 }
index 117ee4bed8aef7f317e69a5dda1e805b450c88ea..b0e9ef493279ca8a153a25ed2404fc49b9894cd6 100644 (file)
@@ -358,7 +358,10 @@ void PyModuleRegistry::active_start(
     int r = active_modules->start_one(i.first,
             i.second->pClass,
             i.second->pMyThreadState);
-    assert(r == 0); // TODO
+    if (r != 0) {
+      derr << "Failed to run module in active mode ('" << i.first << "')"
+           << dendl;
+    }
   }
 }
 
@@ -381,6 +384,28 @@ void PyModuleRegistry::shutdown()
     standby_modules.reset();
   }
 
+  // Ideally, now, we'd be able to do this for all modules:
+  //
+  //    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.
+
   modules.clear();
 
   PyEval_RestoreThread(pMainThreadState);
diff --git a/src/mgr/PyModuleRunner.cc b/src/mgr/PyModuleRunner.cc
new file mode 100644 (file)
index 0000000..c20199e
--- /dev/null
@@ -0,0 +1,97 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2016 John Spray <john.spray@redhat.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ */
+
+
+// Python.h comes first because otherwise it clobbers ceph's assert
+#include "Python.h"
+
+#include "common/debug.h"
+#include "mgr/Gil.h"
+
+#include "PyModuleRunner.h"
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_mgr
+
+
+std::string handle_pyerror();
+
+PyModuleRunner::~PyModuleRunner()
+{
+  Gil gil(pMyThreadState);
+
+  if (pClassInstance) {
+    Py_XDECREF(pClassInstance);
+    pClassInstance = nullptr;
+  }
+
+  Py_DECREF(pClass);
+  pClass = nullptr;
+}
+
+int PyModuleRunner::serve()
+{
+  assert(pClassInstance != nullptr);
+
+  // 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);
+
+  int r = 0;
+  if (pValue != NULL) {
+    Py_DECREF(pValue);
+  } else {
+    derr << module_name << ".serve:" << dendl;
+    derr << handle_pyerror() << dendl;
+    return -EINVAL;
+  }
+
+  return r;
+}
+
+void PyModuleRunner::shutdown()
+{
+  assert(pClassInstance != nullptr);
+
+  Gil gil(pMyThreadState);
+
+  auto pValue = PyObject_CallMethod(pClassInstance,
+      const_cast<char*>("shutdown"), nullptr);
+
+  if (pValue != NULL) {
+    Py_DECREF(pValue);
+  } else {
+    derr << "Failed to invoke shutdown() on " << module_name << dendl;
+    derr << handle_pyerror() << dendl;
+  }
+}
+
+void PyModuleRunner::log(int level, const std::string &record)
+{
+#undef dout_prefix
+#define dout_prefix *_dout << "mgr[" << module_name << "] "
+  dout(level) << record << dendl;
+#undef dout_prefix
+#define dout_prefix *_dout << "mgr " << __func__ << " "
+}
+
+void* PyModuleRunner::PyModuleRunnerThread::entry()
+{
+  // No need to acquire the GIL here; the module does it.
+  dout(4) << "Entering thread for " << mod->get_name() << dendl;
+  mod->serve();
+  return nullptr;
+}
diff --git a/src/mgr/PyModuleRunner.h b/src/mgr/PyModuleRunner.h
new file mode 100644 (file)
index 0000000..1cb0506
--- /dev/null
@@ -0,0 +1,76 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2016 John Spray <john.spray@redhat.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ */
+
+
+#pragma once
+
+#include "common/Thread.h"
+#include "mgr/Gil.h"
+
+/**
+ * Implement the pattern of calling serve() on a module in a thread,
+ * until shutdown() is called.
+ */
+class PyModuleRunner
+{
+protected:
+  const std::string module_name;
+
+  // Passed in by whoever loaded our python module and looked up
+  // the symbols in it.
+  PyObject *pClass = nullptr;
+
+  // Passed in by whoever created our subinterpreter for us
+  SafeThreadState pMyThreadState = nullptr;
+
+  // Populated when we construct our instance of pClass in load()
+  PyObject *pClassInstance = nullptr;
+
+  class PyModuleRunnerThread : public Thread
+  {
+    PyModuleRunner *mod;
+
+  public:
+    PyModuleRunnerThread(PyModuleRunner *mod_)
+      : mod(mod_) {}
+
+    void *entry() override;
+  };
+
+public:
+  int serve();
+  void shutdown();
+  void log(int level, const std::string &record);
+
+  PyModuleRunner(
+      const std::string &module_name_,
+      PyObject *pClass_,
+      PyThreadState *pMyThreadState_)
+    : 
+      module_name(module_name_),
+      pClass(pClass_), pMyThreadState(pMyThreadState_),
+      thread(this)
+  {
+    assert(pClass != nullptr);
+    assert(pMyThreadState != nullptr);
+    assert(!module_name.empty());
+  }
+
+  ~PyModuleRunner();
+
+  PyModuleRunnerThread thread;
+
+  std::string const &get_name() const { return module_name; }
+};
+
+
index ef17dbbeb9c89493630cfe9ee11a8087cde6fcde..3992553bd440891f685fe99004f24ab27a2d8416 100644 (file)
 // Declaration fulfilled by ActivePyModules
 std::string handle_pyerror();
 
-void* PyModuleRunner::PyModuleRunnerThread::entry()
-{
-  // No need to acquire the GIL here; the module does it.
-  dout(4) << "Entering thread for " << mod->get_name() << dendl;
-  mod->serve();
-  return nullptr;
-}
 
 StandbyPyModules::StandbyPyModules(MonClient *monc_, const MgrMap &mgr_map_)
     : monc(monc_), load_config_thread(monc, &state)
@@ -138,19 +131,6 @@ int StandbyPyModule::load()
   }
 }
 
-StandbyPyModule::~StandbyPyModule()
-{
-  Gil gil(pMyThreadState);
-
-  if (pClassInstance) {
-    Py_XDECREF(pClassInstance);
-    pClassInstance = nullptr;
-  }
-
-  Py_DECREF(pClass);
-  pClass = nullptr;
-}
-
 void *StandbyPyModules::LoadConfigThread::entry()
 {
   dout(10) << "listing keys" << dendl;
@@ -217,52 +197,3 @@ std::string StandbyPyModule::get_active_uri() const
   return result;
 }
 
-int PyModuleRunner::serve()
-{
-  assert(pClassInstance != nullptr);
-
-  // 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);
-
-  int r = 0;
-  if (pValue != NULL) {
-    Py_DECREF(pValue);
-  } else {
-    derr << module_name << ".serve:" << dendl;
-    derr << handle_pyerror() << dendl;
-    return -EINVAL;
-  }
-
-  return r;
-}
-
-void PyModuleRunner::shutdown()
-{
-  assert(pClassInstance != nullptr);
-
-  Gil gil(pMyThreadState);
-
-  auto pValue = PyObject_CallMethod(pClassInstance,
-      const_cast<char*>("shutdown"), nullptr);
-
-  if (pValue != NULL) {
-    Py_DECREF(pValue);
-  } else {
-    derr << "Failed to invoke shutdown() on " << module_name << dendl;
-    derr << handle_pyerror() << dendl;
-  }
-}
-
-void PyModuleRunner::log(int level, const std::string &record)
-{
-#undef dout_prefix
-#define dout_prefix *_dout << "mgr[" << module_name << "] "
-  dout(level) << record << dendl;
-#undef dout_prefix
-#define dout_prefix *_dout << "mgr " << __func__ << " "
-}
-
index 4b2e47a6127c4a555a7e2dde4776dd39a43cda10..e5896e05ce2911511b52b86f9b724cff23535a66 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "mon/MonClient.h"
 #include "mon/MgrMap.h"
+#include "mgr/PyModuleRunner.h"
 
 typedef std::map<std::string, std::string> PyModuleConfig;
 
@@ -78,59 +79,6 @@ public:
   }
 };
 
-/**
- * Implement the pattern of calling serve() on a module in a thread,
- * until shutdown() is called.
- */
-class PyModuleRunner
-{
-protected:
-  const std::string module_name;
-
-  // Passed in by whoever loaded our python module and looked up
-  // the symbols in it.
-  PyObject *pClass = nullptr;
-
-  // Passed in by whoever created our subinterpreter for us
-  PyThreadState *pMyThreadState = nullptr;
-
-  // Populated when we construct our instance of pClass in load()
-  PyObject *pClassInstance = nullptr;
-
-  class PyModuleRunnerThread : public Thread
-  {
-    PyModuleRunner *mod;
-
-  public:
-    PyModuleRunnerThread(PyModuleRunner *mod_)
-      : mod(mod_) {}
-
-    void *entry() override;
-  };
-
-public:
-  int serve();
-  void shutdown();
-  void log(int level, const std::string &record);
-
-  PyModuleRunner(
-      const std::string &module_name_,
-      PyObject *pClass_,
-      PyThreadState *pMyThreadState_)
-    : 
-      module_name(module_name_),
-      pClass(pClass_), pMyThreadState(pMyThreadState_),
-      thread(this)
-  {
-    assert(pClass != nullptr);
-    assert(pMyThreadState != nullptr);
-    assert(!module_name.empty());
-  }
-
-  PyModuleRunnerThread thread;
-
-  std::string const &get_name() { return module_name; }
-};
 
 class StandbyPyModule : public PyModuleRunner
 {
@@ -149,8 +97,6 @@ class StandbyPyModule : public PyModuleRunner
   {
   }
 
-  ~StandbyPyModule();
-
   bool get_config(const std::string &key, std::string *value) const;
   std::string get_active_uri() const;