From 4f6b32de279a5fb0bd6a21e5eb5ff219ffc0b38d Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 25 Apr 2019 17:47:46 -0400 Subject: [PATCH 1/6] Finalize PyEval_AcquireLock() and PyEval_AcquireThread() properly --- Doc/c-api/init.rst | 31 +++++++++++++++++++++++++++++++ Python/ceval.c | 28 ++++++++++++++++------------ 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 7ef11228a33dcf..7f968e3973e319 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -863,6 +863,7 @@ code, or when embedding the Python interpreter: check if the interpreter is in process of being finalized before calling this function to avoid unwanted termination. + .. c:function:: PyThreadState* PyThreadState_Get() Return the current thread state. The global interpreter lock must be held. @@ -1080,9 +1081,27 @@ All of the following functions must be called after :c:func:`Py_Initialize`. *tstate*, which should not be *NULL*. The lock must have been created earlier. If this thread already has the lock, deadlock ensues. + .. note:: + Calling this function from a thread when the runtime is finalizing + will terminate the thread, even if the thread was not created by Python. + You can use :c:func:`_Py_IsFinalizing` or :func:`sys.is_finalizing` to + check if the interpreter is in process of being finalized before calling + this function to avoid unwanted termination. + + .. versionchanged:: 3.8 + Updated to be consistent with :c:func:`PyEval_RestoreThread`, + :c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`, + and terminate the current thread if called while the interpreter is finalizing. + :c:func:`PyEval_RestoreThread` is a higher-level function which is always available (even when threads have not been initialized). + .. note:: + Calling this function from a thread when the runtime is finalizing + will terminate the thread, even if the thread was not created by Python. + You can use :c:func:`_Py_IsFinalizing` or :func:`sys.is_finalizing` to + check if the interpreter is in process of being finalized before calling + this function to avoid unwanted termination. .. c:function:: void PyEval_ReleaseThread(PyThreadState *tstate) @@ -1106,6 +1125,18 @@ All of the following functions must be called after :c:func:`Py_Initialize`. :c:func:`PyEval_RestoreThread` or :c:func:`PyEval_AcquireThread` instead. + .. note:: + Calling this function from a thread when the runtime is finalizing + will terminate the thread, even if the thread was not created by Python. + You can use :c:func:`_Py_IsFinalizing` or :func:`sys.is_finalizing` to + check if the interpreter is in process of being finalized before calling + this function to avoid unwanted termination. + + .. versionchanged:: 3.8 + Updated to be consistent with :c:func:`PyEval_RestoreThread`, + :c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`, + and terminate the current thread if called while the interpreter is finalizing. + .. c:function:: void PyEval_ReleaseLock() diff --git a/Python/ceval.c b/Python/ceval.c index 342dc10af6a629..1e10cb57e86167 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -76,6 +76,7 @@ static PyObject * special_lookup(PyObject *, _Py_Identifier *); static int check_args_iterable(PyObject *func, PyObject *vararg); static void format_kwargs_error(PyObject *func, PyObject *kwargs); static void format_awaitable_error(PyTypeObject *, int); +static inline void exit_thread_if_finalizing(PyThreadState *); #define NAME_ERROR_MSG \ "name '%.200s' is not defined" @@ -210,6 +211,7 @@ PyEval_AcquireLock(void) if (tstate == NULL) Py_FatalError("PyEval_AcquireLock: current thread state is NULL"); take_gil(tstate); + exit_thread_if_finalizing(tstate); } void @@ -230,6 +232,7 @@ PyEval_AcquireThread(PyThreadState *tstate) /* Check someone has called PyEval_InitThreads() to create the lock */ assert(gil_created()); take_gil(tstate); + exit_thread_if_finalizing(tstate); if (PyThreadState_Swap(tstate) != NULL) Py_FatalError( "PyEval_AcquireThread: non-NULL old thread state"); @@ -298,12 +301,7 @@ PyEval_RestoreThread(PyThreadState *tstate) int err = errno; take_gil(tstate); - /* _Py_Finalizing is protected by the GIL */ - if (_Py_IsFinalizing() && !_Py_CURRENTLY_FINALIZING(tstate)) { - drop_gil(tstate); - PyThread_exit_thread(); - Py_UNREACHABLE(); - } + exit_thread_if_finalizing(tstate); errno = err; PyThreadState_Swap(tstate); @@ -1083,12 +1081,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) take_gil(tstate); /* Check if we should make a quick exit. */ - if (_Py_IsFinalizing() && - !_Py_CURRENTLY_FINALIZING(tstate)) - { - drop_gil(tstate); - PyThread_exit_thread(); - } + exit_thread_if_finalizing(tstate); if (PyThreadState_Swap(tstate) != NULL) Py_FatalError("ceval: orphan tstate"); @@ -5199,6 +5192,17 @@ format_awaitable_error(PyTypeObject *type, int prevopcode) } } +static inline void +exit_thread_if_finalizing(PyThreadState *tstate) +{ + /* _Py_Finalizing is protected by the GIL */ + if (_Py_IsFinalizing() && !_Py_CURRENTLY_FINALIZING(tstate)) { + drop_gil(tstate); + PyThread_exit_thread(); + Py_UNREACHABLE(); + } +} + static PyObject * unicode_concatenate(PyObject *v, PyObject *w, PyFrameObject *f, const _Py_CODEUNIT *next_instr) From 2c0294449aeec4cdc0a330f9196590e30dceaac0 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 25 Apr 2019 18:02:55 -0400 Subject: [PATCH 2/6] Finalize PyEval_AcquireLock() and PyEval_AcquireThread() properly --- Doc/whatsnew/3.8.rst | 7 +++++++ .../2019-04-02-20-02-22.bpo-36475.CjRps3.rst | 4 ++++ 2 files changed, 11 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-04-02-20-02-22.bpo-36475.CjRps3.rst diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 2270334a281b08..1ba71062c66a33 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -725,6 +725,13 @@ Changes in Python behavior older Python versions include the version number, it is recommended to always use the ``sys.platform.startswith('aix')``. (Contributed by M. Felt in :issue:`36588`.) + +* :c:func:`PyEval_AcquireLock` and :c:func:`PyEval_AcquireThread` now + terminate the current thread if called while the interpreter is + finalizing, making them consistent with :c:func:`PyEval_RestoreThread`, + :c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`. If this + behaviour is not desired, guard the call by checking :c:func:`_Py_IsFinalizing` + or :c:func:`sys.is_finalizing`. Changes in the Python API ------------------------- diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-04-02-20-02-22.bpo-36475.CjRps3.rst b/Misc/NEWS.d/next/Core and Builtins/2019-04-02-20-02-22.bpo-36475.CjRps3.rst new file mode 100644 index 00000000000000..6f0975107fa281 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-04-02-20-02-22.bpo-36475.CjRps3.rst @@ -0,0 +1,4 @@ +:c:func:`PyEval_AcquireLock` and :c:func:`PyEval_AcquireThread` now +terminate the current thread if called while the interpreter is +finalizing, making them consistent with :c:func:`PyEval_RestoreThread`, +:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`. \ No newline at end of file From b8a16901977eca951331ad68d7bdc575b4dea10e Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Thu, 25 Apr 2019 18:18:41 -0400 Subject: [PATCH 3/6] space fix --- Doc/whatsnew/3.8.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 1ba71062c66a33..2682900fa30c2a 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -725,7 +725,7 @@ Changes in Python behavior older Python versions include the version number, it is recommended to always use the ``sys.platform.startswith('aix')``. (Contributed by M. Felt in :issue:`36588`.) - + * :c:func:`PyEval_AcquireLock` and :c:func:`PyEval_AcquireThread` now terminate the current thread if called while the interpreter is finalizing, making them consistent with :c:func:`PyEval_RestoreThread`, From f2337be61459e17542e66627ab5475d6c2739436 Mon Sep 17 00:00:00 2001 From: Joannah Nanjekye <33177550+nanjekyejoannah@users.noreply.github.com> Date: Thu, 25 Apr 2019 18:23:48 -0400 Subject: [PATCH 4/6] Remove new line. --- Doc/c-api/init.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 7f968e3973e319..16fed2aeda3fc5 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -863,7 +863,6 @@ code, or when embedding the Python interpreter: check if the interpreter is in process of being finalized before calling this function to avoid unwanted termination. - .. c:function:: PyThreadState* PyThreadState_Get() Return the current thread state. The global interpreter lock must be held. From 5daac23db44fc16590c8cb5c32e0d67c80e66426 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Fri, 26 Apr 2019 13:52:13 -0400 Subject: [PATCH 5/6] Finalize PyEval_AcquireLock() and PyEval_AcquireThread() properly --- Doc/c-api/init.rst | 7 ------- Python/ceval.c | 22 +++++++++++----------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 7f968e3973e319..3b544d3d806df3 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1096,13 +1096,6 @@ All of the following functions must be called after :c:func:`Py_Initialize`. :c:func:`PyEval_RestoreThread` is a higher-level function which is always available (even when threads have not been initialized). - .. note:: - Calling this function from a thread when the runtime is finalizing - will terminate the thread, even if the thread was not created by Python. - You can use :c:func:`_Py_IsFinalizing` or :func:`sys.is_finalizing` to - check if the interpreter is in process of being finalized before calling - this function to avoid unwanted termination. - .. c:function:: void PyEval_ReleaseThread(PyThreadState *tstate) Reset the current thread state to *NULL* and release the global interpreter diff --git a/Python/ceval.c b/Python/ceval.c index 1e10cb57e86167..370170c2275649 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -204,6 +204,17 @@ _PyEval_FiniThreads(void) } } +static inline void +exit_thread_if_finalizing(PyThreadState *tstate) +{ + /* _Py_Finalizing is protected by the GIL */ + if (_Py_IsFinalizing() && !_Py_CURRENTLY_FINALIZING(tstate)) { + drop_gil(tstate); + PyThread_exit_thread(); + Py_UNREACHABLE(); + } +} + void PyEval_AcquireLock(void) { @@ -5192,17 +5203,6 @@ format_awaitable_error(PyTypeObject *type, int prevopcode) } } -static inline void -exit_thread_if_finalizing(PyThreadState *tstate) -{ - /* _Py_Finalizing is protected by the GIL */ - if (_Py_IsFinalizing() && !_Py_CURRENTLY_FINALIZING(tstate)) { - drop_gil(tstate); - PyThread_exit_thread(); - Py_UNREACHABLE(); - } -} - static PyObject * unicode_concatenate(PyObject *v, PyObject *w, PyFrameObject *f, const _Py_CODEUNIT *next_instr) From 4e1d82029c46a7d0656a8e814ea83f4fc3f833b8 Mon Sep 17 00:00:00 2001 From: nanjekyejoannah Date: Fri, 26 Apr 2019 14:03:30 -0400 Subject: [PATCH 6/6] Finalize PyEval_AcquireLock() and PyEval_AcquireThread() properly --- Doc/c-api/init.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index e07043e0ec8f65..367c069a7ff4cd 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1095,6 +1095,7 @@ All of the following functions must be called after :c:func:`Py_Initialize`. :c:func:`PyEval_RestoreThread` is a higher-level function which is always available (even when threads have not been initialized). + .. c:function:: void PyEval_ReleaseThread(PyThreadState *tstate) Reset the current thread state to *NULL* and release the global interpreter