Skip to content

Commit 211edf1

Browse files
committed
Fix a few other bugs.
1 parent 29fa419 commit 211edf1

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

Programs/_testembed.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2935,6 +2935,56 @@ test_thread_state_release_with_destructor(void)
29352935
return 0;
29362936
}
29372937

2938+
static int
2939+
test_thread_state_ensure_from_view_interp_switch(void)
2940+
{
2941+
_testembed_initialize();
2942+
2943+
/* The main tstate is already attached and was NOT created by
2944+
* PyThreadState_Ensure, so delete_on_release == 0. */
2945+
PyInterpreterState *interp = _PyInterpreterState_GET();
2946+
assert(interp != NULL);
2947+
PyInterpreterView *view = PyInterpreterView_FromCurrent();
2948+
assert(view != NULL);
2949+
2950+
/* First Ensure/Release pair on this pre-existing tstate. */
2951+
assert(_PyThreadState_GET() != NULL);
2952+
PyThreadStateToken *t1 = PyThreadState_EnsureFromView(view);
2953+
assert(t1 != NULL);
2954+
assert(_PyInterpreterState_GuardCountdown(interp) == 1);
2955+
PyThreadState_Release(t1);
2956+
assert(_PyInterpreterState_GuardCountdown(interp) == 0);
2957+
assert(_PyThreadState_GET() != NULL);
2958+
2959+
/* tstate->ensure.owned_guard now points at the freed guard. */
2960+
2961+
/* Re-attach: Bug B detaches us as a side effect (separate repro). */
2962+
PyThreadState *save = PyThreadState_Swap(NULL);
2963+
2964+
PyThreadStateToken *t2 = PyThreadState_EnsureFromView(view);
2965+
assert(_PyInterpreterState_GuardCountdown(interp) == 1);
2966+
assert(t2 != NULL);
2967+
PyThreadState_Release(t2);
2968+
assert(_PyInterpreterState_GuardCountdown(interp) == 0);
2969+
assert(_PyThreadState_GET() == NULL);
2970+
2971+
PyThreadState_Swap(save);
2972+
2973+
/* In a release build (no assertion) the second Ensure silently
2974+
* skipped storing its guard and Release decremented the global
2975+
* counter from 0, wrapping it to GUARDS_NOT_ALLOWED. All future
2976+
* guard acquisitions then fail: */
2977+
PyInterpreterGuard *g = PyInterpreterGuard_FromCurrent();
2978+
assert(g != NULL);
2979+
assert(_PyInterpreterState_GuardCountdown(interp) == 1);
2980+
PyInterpreterGuard_Close(g);
2981+
assert(_PyInterpreterState_GuardCountdown(interp) == 0);
2982+
2983+
PyInterpreterView_Close(view);
2984+
Py_Finalize();
2985+
return 0;
2986+
}
2987+
29382988
/* *********************************************************
29392989
* List of test cases and the function that implements it.
29402990
*
@@ -3034,6 +3084,7 @@ static struct TestCase TestCases[] = {
30343084
{"test_thread_state_ensure_from_view", test_thread_state_ensure_from_view},
30353085
{"test_concurrent_finalization_stress", test_concurrent_finalization_stress},
30363086
{"test_thread_state_release_with_destructor", test_thread_state_release_with_destructor},
3087+
{"test_thread_state_ensure_from_view_interp_switch", test_thread_state_ensure_from_view_interp_switch},
30373088
{NULL, NULL}
30383089
};
30393090

Python/pystate.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3484,7 +3484,7 @@ PyThreadState_Ensure(PyInterpreterGuard *guard)
34843484
if (attached_tstate != NULL && attached_tstate->interp == interp) {
34853485
/* Yay! We already have an attached thread state that matches. */
34863486
++attached_tstate->ensure.counter;
3487-
return NO_TSTATE_SENTINEL;
3487+
return attached_tstate;
34883488
}
34893489

34903490
PyThreadState *detached_gilstate = gilstate_get();
@@ -3572,6 +3572,12 @@ PyThreadState_Release(PyThreadStateToken *token)
35723572
--tstate->ensure.counter;
35733573
}
35743574

3575+
// This is usually done by PyThreadState_Clear(), but we need to do it
3576+
// manually if we don't own the thread state.
3577+
if (owned_guard != NULL) {
3578+
tstate->ensure.owned_guard = NULL;
3579+
}
3580+
35753581
PyThreadState *check_tstate = PyThreadState_Swap(to_restore);
35763582
(void)check_tstate;
35773583
assert(check_tstate == tstate);

0 commit comments

Comments
 (0)