On Thu, Jun 09, 2022 at 11:03:02AM -0500, Eric Blake wrote:
On Thu, Jun 09, 2022 at 03:22:42PM +0100, Richard W.M. Jones wrote:
> On Thu, Jun 09, 2022 at 08:34:43AM -0500, Eric Blake wrote:
> > Instead of open-coding our code to create a PyObject wrapper of the
> > mutable *error to be passed to each callback, extract this into a
> > helper function. We can then slightly optimize things to hang on to a
> > single pointer to the ctypes module, rather than looking it up on
> > every callback. The generated code shrinks by more than the added
> > code in utils.c:
> >
> > +++ b/generator/Python.ml
> > @@ -41,6 +41,7 @@ let
> > extern PyObject *nbd_internal_py_get_aio_view (PyObject *, bool);
> > extern int nbd_internal_py_init_aio_buffer (PyObject *);
> > extern PyObject *nbd_internal_py_get_nbd_buffer_type (void);
> > +extern PyObject *nbd_internal_py_wrap_errptr (int);
> >
> > static inline struct nbd_handle *
> > get_handle (PyObject *obj)
> > @@ -184,13 +185,7 @@ let
> > | CBInt _
> > | CBInt64 _ -> ()
> > | CBMutable (Int n) ->
> > - pr " PyObject *py_%s_modname = PyUnicode_FromString
(\"ctypes\");\n" n;
> > - pr " if (!py_%s_modname) { PyErr_PrintEx (0); goto out; }\n"
n;
> > - pr " PyObject *py_%s_mod = PyImport_Import
(py_%s_modname);\n" n n;
> > - pr " Py_DECREF (py_%s_modname);\n" n;
> > - pr " if (!py_%s_mod) { PyErr_PrintEx (0); goto out; }\n" n;
> > - pr " py_%s = PyObject_CallMethod (py_%s_mod, \"c_int\",
\"i\", *%s);\n" n n n;
> > - pr " Py_DECREF (py_%s_mod);\n" n;
The old code was using PyObject_CallMethod outside the GIL lock...
> > + pr " py_%s = nbd_internal_py_wrap_errptr (*%s);\n" n n;
> > pr " if (!py_%s) { PyErr_PrintEx (0); goto out; }\n" n;
> > | CBString _
> > | CBUInt _
> > diff --git a/python/utils.c b/python/utils.c
> > index e0df181..cd44b90 100644
> > --- a/python/utils.c
> > +++ b/python/utils.c
> > @@ -173,3 +173,22 @@ nbd_internal_py_get_nbd_buffer_type (void)
> > }
> > return type;
> > }
> > +
> > +/* Helper to package callback *error into modifiable PyObject */
> > +PyObject *
> > +nbd_internal_py_wrap_errptr (int err)
> > +{
> > + static PyObject *py_ctypes_mod;
> > +
> > + if (!py_ctypes_mod) {
> > + PyObject *py_modname = PyUnicode_FromString ("ctypes");
> > + if (!py_modname)
> > + return NULL;
> > + py_ctypes_mod = PyImport_Import (py_modname);
> > + Py_DECREF (py_modname);
> > + if (!py_ctypes_mod)
> > + return NULL;
...the new code as well.
> > + }
> > +
> > + return PyObject_CallMethod (py_ctypes_mod, "c_int", "i",
err);
> > +}
>
> I guess because of the Python GIL we don't need locking here?
But you're right that any time we call into the python interpreter for
something that may entail quite some level of complexity, the GIL lock
needs to be worried about. I'll have to audit this and other recent
patches to see if we need to add more use of
PyGILState_{Ensure/Release} around various code blocks (after first
researching what the lock is supposed to do...).
Okay, after reading more about the GIL, it looks like we are (likely)
safe only because we have never really been multi-threaded: none of
our libnbd code uses Py_BEGIN_ALLOW_THREADS to release the GIL around
our calls into libnbd C code, and therefore even though our placement
of PyGILState_Ensure() was too late, all of our callbacks have been
reached from places where we already had the GIL (well, hopefully
that's true). At any rate, I'm working on a patch to explicitly
release the GIL around calls into libnbd C code, which means we DO
need to fix our callback wrappers to grab it at the right point. But
the code in utils.c is indeed safe because of the GIL.
--
Eric Blake, Principal Software Engineer
Red Hat, Inc. +1-919-301-3266
Virtualization:
qemu.org |
libvirt.org