Skip to content

Commit 59f10e8

Browse files
committed
pythongh-124622: Add PyGILState_EnsureOrFail() function
Add new "OrFail" functions: * _PyEval_AcquireLockOrFail() * _PyEval_RestoreThreadOrFail() * _PyThreadState_AttachOrFail() * take_gil_or_fail()
1 parent a25042e commit 59f10e8

File tree

7 files changed

+104
-20
lines changed

7 files changed

+104
-20
lines changed

Doc/c-api/init.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,15 @@ with sub-interpreters:
12791279
Hangs the current thread, rather than terminating it, if called while the
12801280
interpreter is finalizing.
12811281
1282+
1283+
.. c:function:: int PyGILState_EnsureOrFail(PyGILState_STATE *state)
1284+
1285+
Similar to :c:func:`PyGILState_Ensure`, but *state* is an argument
1286+
and return ``-1`` if the thread must exit. Return ``0`` on success.
1287+
1288+
.. versionadded:: next
1289+
1290+
12821291
.. c:function:: void PyGILState_Release(PyGILState_STATE)
12831292
12841293
Release any resources previously acquired. After this call, Python's state will

Doc/whatsnew/3.14.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,9 @@ New features
13341334
and get an attribute of the module.
13351335
(Contributed by Victor Stinner in :gh:`128911`.)
13361336

1337+
* Add :c:func:`PyGILState_EnsureOrFail` function: similar to
1338+
:c:func:`PyGILState_Ensure`, but return ``-1`` if the thread must exit.
1339+
(Contributed by Victor Stinner in :gh:`124622`.)
13371340

13381341
Limited C API changes
13391342
---------------------

Include/internal/pycore_ceval.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ void _Py_unset_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit);
327327

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

330+
extern int _PyEval_AcquireLockOrFail(PyThreadState *tstate);
331+
extern int _PyEval_RestoreThreadOrFail(PyThreadState *tstate);
330332

331333
#ifdef __cplusplus
332334
}

Include/internal/pycore_pystate.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ _Py_AssertHoldsTstateFunc(const char *func)
313313
#define _Py_AssertHoldsTstate()
314314
#endif
315315

316+
extern int _PyThreadState_AttachOrFail(PyThreadState *tstate);
317+
316318
#ifdef __cplusplus
317319
}
318320
#endif
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`PyGILState_EnsureOrFail` function: similar to
2+
:c:func:`PyGILState_Ensure`, but return ``-1`` if the thread must exit.
3+
Patch by Victor Stinner.

Python/ceval_gil.c

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,15 @@ drop_gil(PyInterpreterState *interp, PyThreadState *tstate, int final_release)
277277

278278
/* Take the GIL.
279279
280+
Return 0 on success.
281+
Return -1 if the thread must exit.
282+
280283
The function saves errno at entry and restores its value at exit.
281284
It may hang rather than return if the interpreter has been finalized.
282285
283286
tstate must be non-NULL. */
284-
static void
285-
take_gil(PyThreadState *tstate)
287+
static int
288+
take_gil_or_fail(PyThreadState *tstate)
286289
{
287290
int err = errno;
288291

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

310313
assert(_PyThreadState_CheckConsistency(tstate));
311314
PyInterpreterState *interp = tstate->interp;
312315
struct _gil_runtime_state *gil = interp->ceval.gil;
313316
#ifdef Py_GIL_DISABLED
314317
if (!_Py_atomic_load_int_relaxed(&gil->enabled)) {
315-
return;
318+
goto done;
316319
}
317320
#endif
318321

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

@@ -366,7 +367,7 @@ take_gil(PyThreadState *tstate)
366367
// return.
367368
COND_SIGNAL(gil->cond);
368369
MUTEX_UNLOCK(gil->mutex);
369-
return;
370+
goto done;
370371
}
371372
#endif
372373

@@ -401,7 +402,7 @@ take_gil(PyThreadState *tstate)
401402
/* tstate could be a dangling pointer, so don't pass it to
402403
drop_gil(). */
403404
drop_gil(interp, NULL, 1);
404-
PyThread_hang_thread();
405+
goto tstate_must_exit;
405406
}
406407
assert(_PyThreadState_CheckConsistency(tstate));
407408

@@ -411,8 +412,25 @@ take_gil(PyThreadState *tstate)
411412

412413
MUTEX_UNLOCK(gil->mutex);
413414

415+
#ifdef Py_GIL_DISABLED
416+
done:
417+
#endif
414418
errno = err;
415-
return;
419+
return 0;
420+
421+
tstate_must_exit:
422+
errno = err;
423+
return -1;
424+
}
425+
426+
static void
427+
take_gil(PyThreadState *tstate)
428+
{
429+
if (take_gil_or_fail(tstate) < 0) {
430+
// gh-87135: hang the thread as *thread_exit() is not a safe
431+
// API. It lacks stack unwind and local variable destruction.
432+
PyThread_hang_thread();
433+
}
416434
}
417435

418436
void _PyEval_SetSwitchInterval(unsigned long microseconds)
@@ -586,6 +604,13 @@ _PyEval_AcquireLock(PyThreadState *tstate)
586604
take_gil(tstate);
587605
}
588606

607+
int
608+
_PyEval_AcquireLockOrFail(PyThreadState *tstate)
609+
{
610+
_Py_EnsureTstateNotNULL(tstate);
611+
return take_gil_or_fail(tstate);
612+
}
613+
589614
void
590615
_PyEval_ReleaseLock(PyInterpreterState *interp,
591616
PyThreadState *tstate,
@@ -641,19 +666,32 @@ PyEval_SaveThread(void)
641666
return tstate;
642667
}
643668

644-
void
645-
PyEval_RestoreThread(PyThreadState *tstate)
669+
670+
int
671+
_PyEval_RestoreThreadOrFail(PyThreadState *tstate)
646672
{
647673
#ifdef MS_WINDOWS
648674
int err = GetLastError();
649675
#endif
650676

651677
_Py_EnsureTstateNotNULL(tstate);
652-
_PyThreadState_Attach(tstate);
678+
if (_PyThreadState_AttachOrFail(tstate) < 0) {
679+
return -1;
680+
}
653681

654682
#ifdef MS_WINDOWS
655683
SetLastError(err);
656684
#endif
685+
return 0;
686+
}
687+
688+
689+
void
690+
PyEval_RestoreThread(PyThreadState *tstate)
691+
{
692+
if (_PyEval_RestoreThreadOrFail(tstate) < 0) {
693+
PyThread_hang_thread();
694+
}
657695
}
658696

659697

Python/pystate.c

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,8 +2060,8 @@ tstate_wait_attach(PyThreadState *tstate)
20602060
} while (!tstate_try_attach(tstate));
20612061
}
20622062

2063-
void
2064-
_PyThreadState_Attach(PyThreadState *tstate)
2063+
int
2064+
_PyThreadState_AttachOrFail(PyThreadState *tstate)
20652065
{
20662066
#if defined(Py_DEBUG)
20672067
// This is called from PyEval_RestoreThread(). Similar
@@ -2076,7 +2076,9 @@ _PyThreadState_Attach(PyThreadState *tstate)
20762076

20772077

20782078
while (1) {
2079-
_PyEval_AcquireLock(tstate);
2079+
if (_PyEval_AcquireLockOrFail(tstate) < 0) {
2080+
return -1;
2081+
}
20802082

20812083
// XXX assert(tstate_is_alive(tstate));
20822084
current_fast_set(&_PyRuntime, tstate);
@@ -2111,6 +2113,15 @@ _PyThreadState_Attach(PyThreadState *tstate)
21112113
#if defined(Py_DEBUG)
21122114
errno = err;
21132115
#endif
2116+
return 0;
2117+
}
2118+
2119+
void
2120+
_PyThreadState_Attach(PyThreadState *tstate)
2121+
{
2122+
if (_PyThreadState_AttachOrFail(tstate) < 0) {
2123+
PyThread_hang_thread();
2124+
}
21142125
}
21152126

21162127
static void
@@ -2730,8 +2741,9 @@ PyGILState_Check(void)
27302741
return (tstate == tcur);
27312742
}
27322743

2733-
PyGILState_STATE
2734-
PyGILState_Ensure(void)
2744+
2745+
int
2746+
PyGILState_EnsureOrFail(PyGILState_STATE *state)
27352747
{
27362748
_PyRuntimeState *runtime = &_PyRuntime;
27372749

@@ -2770,7 +2782,9 @@ PyGILState_Ensure(void)
27702782
}
27712783

27722784
if (!has_gil) {
2773-
PyEval_RestoreThread(tcur);
2785+
if (_PyEval_RestoreThreadOrFail(tcur) < 0) {
2786+
return -1;
2787+
}
27742788
}
27752789

27762790
/* Update our counter in the thread-state - no need for locks:
@@ -2780,9 +2794,22 @@ PyGILState_Ensure(void)
27802794
*/
27812795
++tcur->gilstate_counter;
27822796

2783-
return has_gil ? PyGILState_LOCKED : PyGILState_UNLOCKED;
2797+
*state = has_gil ? PyGILState_LOCKED : PyGILState_UNLOCKED;
2798+
return 0;
2799+
}
2800+
2801+
2802+
PyGILState_STATE
2803+
PyGILState_Ensure(void)
2804+
{
2805+
PyGILState_STATE state;
2806+
if (PyGILState_EnsureOrFail(&state) < 0) {
2807+
PyThread_hang_thread();
2808+
}
2809+
return state;
27842810
}
27852811

2812+
27862813
void
27872814
PyGILState_Release(PyGILState_STATE oldstate)
27882815
{

0 commit comments

Comments
 (0)