[Libguestfs] [libnbd PATCH] python: Correctly use PyGILState

Richard W.M. Jones rjones at redhat.com
Fri Jun 10 07:31:43 UTC 2022


On Thu, Jun 09, 2022 at 04:26:30PM -0500, Eric Blake wrote:
> The python docs are clear that in a multi-threaded app, we CANNOT call
> any python functions from C code (other than a short-list of
> exceptions) without first owning the GIL in the calling thread.
> Although many users of nbdsh are single-threaded (where the GIL does
> not matter), it is indeed possible to write a python script that
> creates python threads to manage h.poll(-1) in one thread while using
> other threads to trigger aio_pread and aio_pwrite requests, so
> callbacks can be reached from a different thread than the
> corresponding thread that initiated the nbd I/O request.
> 
> Unfortunately, our generated code was not doing it correctly (for
> example, debug_wrapper() to handle h.set_debug_callback() calls
> Py_BuildValue outside PyGILState_Ensure()).  But it appears that we
> have so far been lucky that our callback actions generally occur based
> on state machine actions triggered by another Python-based action in
> the same thread, coupled with the fact that we have not been
> explicitly releasing the GIL around calls into libnbd C API, so in
> practice all our callbacks have already owned the GIL in spite of
> coding it in the wrong place.  However, I cannot rule out the risk of
> a mysterious crash in a multi-threaded python program using the nbd
> module.  Worse, holding the GIL across a potentially-blocking C
> operation is bad form; no other Python thread can make progress if we
> don't release the GIL.
> 
> Fix this by widening the scope of PyGILState in all callback wrappers
> (all other code calling into Python is assumed to already own the
> GIL), and by dropping the GIL around calls into libnbd C code.
> 
> Note that the Python docs also state that manipulation of PyGILState
> is incompatible with the use of sub-interpreters with
> Py_NewInterpreter(); but if someone actually wants to write a complex
> program that uses sub-interpreters to interact with libnbd via Python
> independently from some other use of python, rather than just directly
> interacting with libnbd via C, I'd be surprised.
> 
> Fixes: 936488d4 ("python: Implement Callback properly.", v0.1)
> ---
>  generator/Python.ml | 11 +++++++----
>  1 file changed, 7 insertions(+), 4 deletions(-)
> 
> diff --git a/generator/Python.ml b/generator/Python.ml
> index e94f37b..f196e8e 100644
> --- a/generator/Python.ml
> +++ b/generator/Python.ml
> @@ -158,7 +158,7 @@ let
>    pr "  const struct user_data *data = user_data;\n";
>    pr "  int ret = -1;\n";
>    pr "\n";
> -  pr "  PyGILState_STATE py_save = PyGILState_UNLOCKED;\n";
> +  pr "  PyGILState_STATE py_save = PyGILState_Ensure();\n";
>    pr "  PyObject *py_args, *py_ret;\n";
>    List.iter (
>      function
> @@ -227,9 +227,7 @@ let
>    pr ");\n";
>    pr "  if (!py_args) { PyErr_PrintEx (0); goto out; }\n";
>    pr "\n";
> -  pr "  py_save = PyGILState_Ensure ();\n";
>    pr "  py_ret = PyObject_CallObject (data->fn, py_args);\n";
> -  pr "  PyGILState_Release (py_save);\n";
>    pr "\n";
>    pr "  Py_DECREF (py_args);\n";
>    pr "\n";
> @@ -268,6 +266,7 @@ let
>      | CBUInt _ | CBUInt64 _ -> ()
>      | CBArrayAndLen _ | CBMutable _ -> assert false
>    ) cbargs;
> +  pr "  PyGILState_Release (py_save);\n";
>    pr "  return ret;\n";
>    pr "}\n";
>    pr "\n"
> @@ -478,7 +477,8 @@ let
>      | BytesPersistOut (n, _) ->
>         pr "  if (nbd_internal_py_init_aio_buffer (%s) < 0) goto out;\n" n
>      | _ -> ()
> -  ) args;
> +    ) args;
> +  pr "  Py_BEGIN_ALLOW_THREADS\n";
>    pr "  ret = nbd_%s (" name;
>    pr_wrap ',' (fun () ->
>        pr "h";
> @@ -487,6 +487,7 @@ let
>          | _, _, n -> pr ", %s" n
>          ) params);
>    pr ");\n";
> +  pr "  Py_END_ALLOW_THREADS\n";
>    List.iter (
>      function
>      | Closure { cbname } -> pr "  %s_user_data = NULL;\n" cbname
> @@ -613,8 +614,10 @@ let
>    pr "  struct user_data *data = user_data;\n";
>    pr "\n";
>    pr "  if (data) {\n";
> +  pr "    PyGILState_STATE py_save = PyGILState_Ensure();\n";
>    pr "    Py_XDECREF (data->fn);\n";
>    pr "    Py_XDECREF (data->buf);\n";
> +  pr "    PyGILState_Release (py_save);\n";
>    pr "    free (data);\n";
>    pr "  }\n";
>    pr "}\n";

Reviewed-by: Richard W.M. Jones <rjones at redhat.com>

although maybe ...

https://gitlab.com/nbdkit/nbdkit/-/blob/c96f025b39a3581405845004e1fcceb5dfa04583/plugins/python/plugin.h#L45

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
Fedora Windows cross-compiler. Compile Windows programs, test, and
build Windows installers. Over 100 libraries supported.
http://fedoraproject.org/wiki/MinGW


More information about the Libguestfs mailing list