* Foundation. See file COPYING.
*/
+#include "PyState.h"
#include "Gil.h"
#include "PyFormatter.h"
#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_, PyThreadState *main_ts_)
+MgrPyModule::MgrPyModule(const std::string &module_name_, const std::string &sys_path, PyThreadState *main_ts_)
: module_name(module_name_),
pClassInstance(nullptr),
pMainThreadState(main_ts_)
{
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);
+
+ PySys_SetPath(const_cast<char*>(sys_path.c_str()));
+ }
}
MgrPyModule::~MgrPyModule()
{
- Gil gil(pMainThreadState);
-
- Py_XDECREF(pClassInstance);
+ 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()
{
- Gil gil(pMainThreadState);
+ 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());
// 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(pMainThreadState, true);
+ Gil gil(pMyThreadState, true);
auto pValue = PyObject_CallMethod(pClassInstance,
const_cast<char*>("serve"), nullptr);
{
assert(pClassInstance != nullptr);
- Gil gil(pMainThreadState);
+ Gil gil(pMyThreadState);
auto pValue = PyObject_CallMethod(pClassInstance,
const_cast<char*>("shutdown"), nullptr);
{
assert(pClassInstance != nullptr);
- Gil gil(pMainThreadState);
+ Gil gil(pMyThreadState);
// Execute
auto pValue = PyObject_CallMethod(pClassInstance,
{
assert(pClassInstance != nullptr);
- Gil gil(pMainThreadState);
+ Gil gil(pMyThreadState);
// Construct python-ized LogEntry
PyFormatter f;
assert(ss != nullptr);
assert(ds != nullptr);
- Gil gil(pMainThreadState);
+ Gil gil(pMyThreadState);
PyFormatter f;
cmdmap_dump(cmdmap, &f);
#define dout_context g_ceph_context
#define dout_subsys ceph_subsys_mgr
-
#undef dout_prefix
-#define dout_prefix *_dout << "mgr[py] "
+#define dout_prefix *_dout << "mgr " << __func__ << " "
// definition for non-const static member
std::string PyModules::config_prefix;
-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__ << " "
-
// constructor/destructor implementations cannot be in .h,
// because ServeThread is still an "incomplete" type there
Py_SetProgramName(const_cast<char*>(PYTHON_EXECUTABLE));
Py_InitializeEx(0);
- // 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
+ // Let CPython know that we will be calling it back from other
+ // threads in future.
+ if (! PyEval_ThreadsInitialized()) {
+ PyEval_InitThreads();
}
- // Populate python namespace with callable hooks
- Py_InitModule("ceph_state", CephStateMethods);
// Configure sys.path to include mgr_module_path
std::string sys_path = std::string(Py_GetPath()) + ":" + get_site_packages()
+ ":" + g_conf->mgr_module_path;
dout(10) << "Computed sys.path '" << sys_path << "'" << dendl;
- PySys_SetPath((char*)(sys_path.c_str()));
-
- // Let CPython know that we will be calling it back from other
- // threads in future.
- if (! PyEval_ThreadsInitialized()) {
- PyEval_InitThreads();
- }
// Drop the GIL and remember the main thread state (current
// thread state becomes NULL)
boost::tokenizer<> tok(g_conf->mgr_modules);
for(const auto& module_name : tok) {
dout(1) << "Loading python module '" << module_name << "'" << dendl;
- auto mod = std::unique_ptr<MgrPyModule>(new MgrPyModule(module_name, pMainThreadState));
+ auto mod = std::unique_ptr<MgrPyModule>(new MgrPyModule(module_name, sys_path, pMainThreadState));
int r = mod->load();
if (r != 0) {
// Don't use handle_pyerror() here; we don't have the GIL
- // or a thread state (this is deliberate).
+ // or the right thread state (this is deliberate).
derr << "Error loading module '" << module_name << "': "
<< cpp_strerror(r) << dendl;
// Don't drop out here, load the other modules
// Success!
modules[module_name] = std::move(mod);
}
- }
+ }
return 0;
}