Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Doc/c-api/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,7 @@ with sub-interpreters:
Hangs the current thread, rather than terminating it, if called while the
interpreter is finalizing.


.. c:function:: void PyGILState_Release(PyGILState_STATE)

Release any resources previously acquired. After this call, Python's state will
Expand Down Expand Up @@ -1499,6 +1500,37 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
.. versionadded:: 3.8


.. c:function:: int PyThreadState_Ensure(PyInterpreterState *interp, const char **errmsg)

Similar to :c:func:`PyGILState_Ensure`, except that it returns with a status
code even in the case of failure, and takes an interpreter state.
Specifically, it returns a status code (``>= 0``) when the operation
succeeded, or sets *\*errmsg* (if *errmsg* is not NULL) and returns ``-1``
on failure.

On success, the thread state must be released by
:c:func:`PyThreadState_Release`.

In the case of failure, it is *unsafe* to use the Python API following the
call. Releasing the obtained *state* via :c:func:`PyGILState_Release` must
only be done in the case of success.

.. versionadded:: next


.. c:function:: void PyThreadState_Release(int state)

Release any resources previously acquired. After this call, Python's state
will be the same as it was prior to the corresponding
:c:func:`PyThreadState_Ensure` call (but generally this state will be
unknown to the caller).

Every call to :c:func:`PyThreadState_Ensure` must be matched by a call to
:c:func:`PyThreadState_Release` on the same thread.

.. versionadded:: next


.. c:function:: PyObject* PyUnstable_InterpreterState_GetMainModule(PyInterpreterState *interp)

Return a :term:`strong reference` to the ``__main__`` :ref:`module object <moduleobjects>`
Expand Down
2 changes: 2 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,11 @@ New features
and get an attribute of the module.
(Contributed by Victor Stinner in :gh:`128911`.)

* Add :c:func:`PyThreadState_Ensure` and :c:func:`PyThreadState_Release`
functions: similar to :c:func:`PyGILState_Ensure` and
:c:func:`PyGILState_Release`, but :c:func:`PyThreadState_Ensure` returns
``-1`` on failure. Patch by Victor Stinner.
(Contributed by Victor Stinner in :gh:`124622`.)

Limited C API changes
---------------------
Expand Down
6 changes: 6 additions & 0 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,12 @@ void _Py_unset_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit);

PyAPI_FUNC(PyObject *) _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyStackRef right, double value);

extern int _PyEval_AcquireLockOrFail(
PyThreadState *tstate,
const char **errmsg);
extern int _PyEval_RestoreThreadOrFail(
PyThreadState *tstate,
const char **errmsg);

#ifdef __cplusplus
}
Expand Down
4 changes: 4 additions & 0 deletions Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ _Py_AssertHoldsTstateFunc(const char *func)
#define _Py_AssertHoldsTstate()
#endif

extern int _PyThreadState_AttachOrFail(
PyThreadState *tstate,
const char **errmsg);

#ifdef __cplusplus
}
#endif
Expand Down
8 changes: 8 additions & 0 deletions Include/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ PyAPI_FUNC(PyGILState_STATE) PyGILState_Ensure(void);
*/
PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE);

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
/* New in 3.14 */
PyAPI_FUNC(int) PyThreadState_Ensure(
PyInterpreterState *interp,
const char **errmsg);
PyAPI_FUNC(void) PyThreadState_Release(int state);
#endif

/* Helper/diagnostic function - get the current thread state for
this thread. May return NULL if no GILState API has been used
on the current thread. Note that the main thread always has such a
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add :c:func:`PyThreadState_Ensure` and :c:func:`PyThreadState_Release`
functions: similar to :c:func:`PyGILState_Ensure` and
:c:func:`PyGILState_Release`, but :c:func:`PyThreadState_Ensure` returns ``-1``
on failure. Patch by Victor Stinner.
4 changes: 4 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2545,3 +2545,7 @@
added = '3.14'
[function.Py_PACK_VERSION]
added = '3.14'
[function.PyThreadState_Ensure]
added = '3.14'
[function.PyThreadState_Release]
added = '3.14'
12 changes: 9 additions & 3 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1263,19 +1263,24 @@ typedef struct {
PyThread_type_lock start_event;
PyThread_type_lock exit_event;
PyObject *callback;
PyInterpreterState *interp;
} test_c_thread_t;

static void
temporary_c_thread(void *data)
{
test_c_thread_t *test_c_thread = data;
PyGILState_STATE state;
PyObject *res;

PyThread_release_lock(test_c_thread->start_event);

/* Allocate a Python thread state for this thread */
state = PyGILState_Ensure();
const char *errmsg;
int state = PyThreadState_Ensure(test_c_thread->interp, &errmsg);
if (state < 0) {
fprintf(stderr, "ERROR: PyThreadState_Ensure() failed: %s", errmsg);
abort();
}

res = PyObject_CallNoArgs(test_c_thread->callback);
Py_CLEAR(test_c_thread->callback);
Expand All @@ -1288,7 +1293,7 @@ temporary_c_thread(void *data)
}

/* Destroy the Python thread state for this thread */
PyGILState_Release(state);
PyThreadState_Release(state);

PyThread_release_lock(test_c_thread->exit_event);
}
Expand All @@ -1310,6 +1315,7 @@ call_in_temporary_c_thread(PyObject *self, PyObject *args)
test_c_thread.start_event = PyThread_allocate_lock();
test_c_thread.exit_event = PyThread_allocate_lock();
test_c_thread.callback = NULL;
test_c_thread.interp = PyInterpreterState_Get();
if (!test_c_thread.start_event || !test_c_thread.exit_event) {
PyErr_SetString(PyExc_RuntimeError, "could not allocate lock");
goto exit;
Expand Down
2 changes: 2 additions & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 54 additions & 13 deletions Python/ceval_gil.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,15 @@ drop_gil(PyInterpreterState *interp, PyThreadState *tstate, int final_release)

/* Take the GIL.

Return 0 on success.
Return -1 if the thread must exit.

The function saves errno at entry and restores its value at exit.
It may hang rather than return if the interpreter has been finalized.

tstate must be non-NULL. */
static void
take_gil(PyThreadState *tstate)
static int
take_gil_or_fail(PyThreadState *tstate, const char **errmsg)
{
int err = errno;

Expand All @@ -304,15 +307,15 @@ take_gil(PyThreadState *tstate)
C++. gh-87135: The best that can be done is to hang the thread as
the public APIs calling this have no error reporting mechanism (!).
*/
PyThread_hang_thread();
goto tstate_must_exit;
}

assert(_PyThreadState_CheckConsistency(tstate));
PyInterpreterState *interp = tstate->interp;
struct _gil_runtime_state *gil = interp->ceval.gil;
#ifdef Py_GIL_DISABLED
if (!_Py_atomic_load_int_relaxed(&gil->enabled)) {
return;
goto done;
}
#endif

Expand Down Expand Up @@ -348,9 +351,7 @@ take_gil(PyThreadState *tstate)
if (drop_requested) {
_Py_unset_eval_breaker_bit(holder_tstate, _PY_GIL_DROP_REQUEST_BIT);
}
// gh-87135: hang the thread as *thread_exit() is not a safe
// API. It lacks stack unwind and local variable destruction.
PyThread_hang_thread();
goto tstate_must_exit;
}
assert(_PyThreadState_CheckConsistency(tstate));

Expand All @@ -366,7 +367,7 @@ take_gil(PyThreadState *tstate)
// return.
COND_SIGNAL(gil->cond);
MUTEX_UNLOCK(gil->mutex);
return;
goto done;
}
#endif

Expand Down Expand Up @@ -401,7 +402,7 @@ take_gil(PyThreadState *tstate)
/* tstate could be a dangling pointer, so don't pass it to
drop_gil(). */
drop_gil(interp, NULL, 1);
PyThread_hang_thread();
goto tstate_must_exit;
}
assert(_PyThreadState_CheckConsistency(tstate));

Expand All @@ -411,8 +412,28 @@ take_gil(PyThreadState *tstate)

MUTEX_UNLOCK(gil->mutex);

#ifdef Py_GIL_DISABLED
done:
#endif
errno = err;
return;
return 0;

tstate_must_exit:
if (errmsg) {
*errmsg = "Python is being finalized";
}
errno = err;
return -1;
}

static void
take_gil(PyThreadState *tstate)
{
if (take_gil_or_fail(tstate, NULL) < 0) {
// gh-87135: hang the thread as *thread_exit() is not a safe
// API. It lacks stack unwind and local variable destruction.
PyThread_hang_thread();
}
}

void _PyEval_SetSwitchInterval(unsigned long microseconds)
Expand Down Expand Up @@ -586,6 +607,13 @@ _PyEval_AcquireLock(PyThreadState *tstate)
take_gil(tstate);
}

int
_PyEval_AcquireLockOrFail(PyThreadState *tstate, const char **errmsg)
{
_Py_EnsureTstateNotNULL(tstate);
return take_gil_or_fail(tstate, errmsg);
}

void
_PyEval_ReleaseLock(PyInterpreterState *interp,
PyThreadState *tstate,
Expand Down Expand Up @@ -641,19 +669,32 @@ PyEval_SaveThread(void)
return tstate;
}

void
PyEval_RestoreThread(PyThreadState *tstate)

int
_PyEval_RestoreThreadOrFail(PyThreadState *tstate, const char **errmsg)
{
#ifdef MS_WINDOWS
int err = GetLastError();
#endif

_Py_EnsureTstateNotNULL(tstate);
_PyThreadState_Attach(tstate);
if (_PyThreadState_AttachOrFail(tstate, errmsg) < 0) {
return -1;
}

#ifdef MS_WINDOWS
SetLastError(err);
#endif
return 0;
}


void
PyEval_RestoreThread(PyThreadState *tstate)
{
if (_PyEval_RestoreThreadOrFail(tstate, NULL) < 0) {
PyThread_hang_thread();
}
}


Expand Down
Loading