}
}
-PyObject *ActivePyModule::dispatch_remote(
+std::optional<std::vector<std::byte>> ActivePyModule::dispatch_remote(
const std::string &method,
- PyObject *args,
- PyObject *kwargs,
+ std::span<std::byte const> pickled_args,
+ std::span<std::byte const> pickled_kwargs,
std::string *err)
{
ceph_assert(err != nullptr);
- // Rather than serializing arguments, pass the CPython objects.
- // Works because we happen to know that the subinterpreter
- // implementation shares a GIL, allocator, deallocator and GC state, so
- // it's okay to pass the objects between subinterpreters.
- // But in future this might involve serialization to support a CSP-aware
- // future Python interpreter a la PEP554
+ // deserialize arguments.
Gil gil(py_module->pMyThreadState, true);
+ auto pmodule = py_module->pPickleModule;
+ auto pickled_args_bytes = py_bytes_from_span(pickled_args);
+ auto args = PyObject_CallMethodObjArgs(
+ pmodule,
+ PyUnicode_FromString("loads"),
+ pickled_args_bytes,
+ nullptr);
+ Py_DECREF(pickled_args_bytes);
+ if (args == nullptr) {
+ std::string caller = "ActivePyModule::dispatch_remote "s + method;
+ *err = handle_pyerror(true, get_name(), caller);
+ derr << "Failed to deserialize (pickle.loads) args: " << *err << dendl;
+ return std::nullopt;
+ }
+
+ auto pickled_kwargs_bytes = py_bytes_from_span(pickled_kwargs);
+ auto kwargs = PyObject_CallMethodObjArgs(
+ pmodule,
+ PyUnicode_FromString("loads"),
+ pickled_kwargs_bytes,
+ nullptr);
+ Py_DECREF(pickled_kwargs_bytes);
+ if (kwargs == nullptr) {
+ std::string caller = "ActivePyModule::dispatch_remote "s + method;
+ *err = handle_pyerror(true, get_name(), caller);
+ derr << "Failed to deserialize (pickle.loads) kwargs: " << *err << dendl;
+
+ Py_DECREF(args);
+ return std::nullopt;
+ }
+
// Fire the receiving method
auto boundMethod = PyObject_GetAttrString(pClassInstance, method.c_str());
dout(20) << "Calling " << py_module->get_name()
<< "." << method << "..." << dendl;
- auto remoteResult = PyObject_Call(boundMethod,
+ auto ret = PyObject_Call(boundMethod,
args, kwargs);
Py_DECREF(boundMethod);
-
- if (remoteResult == nullptr) {
+ Py_DECREF(kwargs);
+ Py_DECREF(args);
+ if (ret == nullptr) {
// Because the caller is in a different context, we can't let this
// exception bubble up, need to re-raise it from the caller's
// context later.
std::string caller = "ActivePyModule::dispatch_remote "s + method;
*err = handle_pyerror(true, get_name(), caller);
- } else {
- dout(20) << "Success calling '" << method << "'" << dendl;
+ return std::nullopt;
+ }
+ dout(20) << "Success calling '" << method << "'" << dendl;
+
+ auto pickled_ret = PyObject_CallMethodObjArgs(
+ pmodule,
+ PyUnicode_FromString("dumps"),
+ ret,
+ nullptr);
+ Py_DECREF(ret);
+ if (pickled_ret == nullptr) {
+ std::string caller = "ActivePyModule::dispatch_remote "s + method;
+ *err = handle_pyerror(true, get_name(), caller);
+ derr << "Failed to serialize (pickle.dumps) ret: " << *err << dendl;
+ return std::nullopt;
}
- return remoteResult;
+ std::vector<std::byte> pickled_ret_str = py_bytes_as_vec(pickled_ret);
+ Py_DECREF(pickled_ret);
+ return pickled_ret_str;
}
void ActivePyModule::config_notify()
bool method_exists(const std::string &method) const;
- PyObject *dispatch_remote(
+ std::optional<std::vector<std::byte>> dispatch_remote(
const std::string &method,
- PyObject *args,
- PyObject *kwargs,
+ std::span<std::byte const> pickled_args,
+ std::span<std::byte const> pickled_kwargs,
std::string *err);
int handle_command(
}
}
-PyObject *ActivePyModules::dispatch_remote(
+std::optional<std::vector<std::byte>> ActivePyModules::dispatch_remote(
const std::string &other_module,
const std::string &method,
- PyObject *args,
- PyObject *kwargs,
+ std::span<std::byte const> pickled_args,
+ std::span<std::byte const> pickled_kwargs,
std::string *err)
{
auto mod_iter = modules.find(other_module);
ceph_assert(mod_iter != modules.end());
- return mod_iter->second->dispatch_remote(method, args, kwargs, err);
+ return mod_iter->second->dispatch_remote(
+ method, pickled_args, pickled_kwargs, err);
}
+
bool ActivePyModules::get_config(const std::string &module_name,
const std::string &key, std::string *val) const
{
return modules.at(module_name)->method_exists(method_name);
}
- PyObject *dispatch_remote(
+ std::optional<std::vector<std::byte>> dispatch_remote(
const std::string &other_module,
const std::string &method,
- PyObject *args,
- PyObject *kwargs,
+ std::span<std::byte const> pickled_args,
+ std::span<std::byte const> pickled_kwargs,
std::string *err);
int init();
using std::list;
using std::string;
+using namespace std::literals;
typedef struct {
PyObject_HEAD
static PyObject *
ceph_dispatch_remote(BaseMgrModule *self, PyObject *args)
{
+ // PyArgs_ParseTuple doesn't give us refcounts here
char *other_module = nullptr;
char *method = nullptr;
PyObject *remote_args = nullptr;
return nullptr;
}
+ auto pmodule = self->this_module->py_module->pPickleModule;
+ auto pickled_args = PyObject_CallMethodObjArgs(
+ pmodule,
+ PyUnicode_FromString("dumps"),
+ remote_args,
+ nullptr);
+ if (pickled_args == nullptr) {
+ std::string caller = "ceph_dispatch_remote "s + " " + method;
+ std::string err = handle_pyerror(true, other_module, caller);
+ PyErr_SetString(PyExc_RuntimeError, err.c_str());
+ derr << err << dendl;
+ return nullptr;
+ }
+ std::span<std::byte const> pickled_args_span = py_bytes_as_span(pickled_args);
+
+ auto pickled_kwargs = PyObject_CallMethodObjArgs(
+ pmodule,
+ PyUnicode_FromString("dumps"),
+ remote_kwargs,
+ nullptr);
+ if (pickled_kwargs == nullptr) {
+ std::string caller = "ceph_dispatch_remote "s + " " + method;
+ std::string err = handle_pyerror(true, other_module, caller);
+ PyErr_SetString(PyExc_RuntimeError, err.c_str());
+ derr << err << dendl;
+
+ Py_DECREF(pickled_args);
+ return nullptr;
+ }
+ std::span<std::byte const> pickled_kwargs_span =
+ py_bytes_as_span(pickled_kwargs);
+
// Drop GIL from calling python thread state, it will be taken
// both for checking for method existence and for executing method.
PyThreadState *tstate = PyEval_SaveThread();
if (!self->py_modules->method_exists(other_module, method)) {
PyEval_RestoreThread(tstate);
PyErr_SetString(PyExc_NameError, "Method not found");
+
+ Py_DECREF(pickled_args);
+ Py_DECREF(pickled_kwargs);
return nullptr;
}
std::string err;
- auto result = self->py_modules->dispatch_remote(other_module, method,
- remote_args, remote_kwargs, &err);
+ std::optional<std::vector<std::byte>> maybe_pickled_ret =
+ self->py_modules->dispatch_remote(
+ other_module,
+ method,
+ pickled_args_span,
+ pickled_kwargs_span,
+ &err);
PyEval_RestoreThread(tstate);
- if (result == nullptr) {
+ // we retain these references across the dispatch_remote call so that
+ // we can pass string_view and avoid the copy
+ Py_XDECREF(pickled_kwargs);
+ Py_XDECREF(pickled_args);
+
+ if (!maybe_pickled_ret) {
std::stringstream ss;
ss << "Remote method threw exception: " << err;
PyErr_SetString(PyExc_RuntimeError, ss.str().c_str());
derr << ss.str() << dendl;
+ return nullptr;
}
- return result;
+ auto pickled_ret_bytes = py_bytes_from_vec(*maybe_pickled_ret);
+ auto ret = PyObject_CallMethodObjArgs(
+ pmodule,
+ PyUnicode_FromString("loads"),
+ pickled_ret_bytes,
+ nullptr);
+ if (ret == nullptr) {
+ std::string caller = "ceph_dispatch_remote "s + " " + method;
+ std::string err = handle_pyerror(true, other_module, caller);
+ PyErr_SetString(PyExc_RuntimeError, err.c_str());
+ derr << err << dendl;
+ }
+ Py_XDECREF(pickled_ret_bytes);
+ return ret;
}
static PyObject*
return exc_msg;
}
+std::span<std::byte const> py_bytes_as_span(PyObject *bytes)
+{
+ assert(bytes);
+ assert(PyBytes_CheckExact(bytes));
+ Py_ssize_t length;
+ char *buf;
+ int r = PyBytes_AsStringAndSize(
+ bytes, &buf, &length);
+ assert(r == 0);
+ return std::span<std::byte const>((const std::byte*)buf, size_t(length));
+}
+
+PyObject *py_bytes_from_span(std::span<std::byte const> s)
+{
+ auto ret = PyBytes_FromStringAndSize(
+ reinterpret_cast<const char*>(s.data()), s.size_bytes());
+ assert(ret);
+ return ret;
+}
+
+std::vector<std::byte> py_bytes_as_vec(PyObject *bytes)
+{
+ assert(bytes);
+ assert(PyBytes_CheckExact(bytes));
+ Py_ssize_t length;
+ char *buf;
+ int r = PyBytes_AsStringAndSize(
+ bytes, &buf, &length);
+ assert(r == 0);
+ return std::vector<std::byte>{
+ reinterpret_cast<const std::byte*>(buf),
+ reinterpret_cast<const std::byte*>(buf) + size_t(length)};
+}
+
+PyObject *py_bytes_from_vec(const std::vector<std::byte> &s)
+{
+ auto ret = PyBytes_FromStringAndSize(
+ reinterpret_cast<const char *>(s.data()), s.size());
+ assert(ret);
+ return ret;
+}
+
namespace {
PyObject* log_write(PyObject*, PyObject* args) {
Gil gil(pMyThreadState);
int r;
+
+ pPickleModule = PyImport_ImportModuleNoBlock("pickle");
+ if (!pPickleModule) {
+ derr << "Unable to load pickle" << dendl;
+ return -EINVAL;
+ }
+
r = load_subclass_of("MgrModule", &pClass);
if (r) {
derr << "Class not found in module '" << module_name << "'" << dendl;
Gil gil(pMyThreadState, true);
Py_XDECREF(pClass);
Py_XDECREF(pStandbyClass);
+ Py_XDECREF(pPickleModule);
}
}
std::string peek_pyerror();
+std::span<std::byte const> py_bytes_as_span(PyObject*);
+PyObject *py_bytes_from_span(std::span<std::byte const>);
+
+std::vector<std::byte> py_bytes_as_vec(PyObject*);
+PyObject *py_bytes_from_vec(const std::vector<std::byte> &);
+
+
/**
* A Ceph CLI command description provided from a Python module
*/
SafeThreadState pMyThreadState;
PyObject *pClass = nullptr;
PyObject *pStandbyClass = nullptr;
+ PyObject *pPickleModule = nullptr;
explicit PyModule(const std::string &module_name_)
: module_name(module_name_)