Skip to content

Commit d5b1346

Browse files
committed
3.14: Save/restore PyInterpreterFrame.stackpointer.
1 parent b54c4bd commit d5b1346

File tree

6 files changed

+43
-40
lines changed

6 files changed

+43
-40
lines changed

src/greenlet/TGreenlet.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ namespace greenlet
125125
// Assertion `tstate->current_executor == NULL' failed.
126126
// see https://github.com/python-greenlet/greenlet/issues/460
127127
PyObject* current_executor;
128+
_PyStackRef* stackpointer;
128129
#ifdef Py_GIL_DISABLED
129130
_PyCStackRef* c_stack_refs;
130131
#endif

src/greenlet/TPythonState.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ PythonState::PythonState()
1515
#if GREENLET_PY314
1616
,py_recursion_depth(0)
1717
,current_executor(nullptr)
18+
,stackpointer(nullptr)
1819
#ifdef Py_GIL_DISABLED
1920
,c_stack_refs(nullptr)
2021
#endif
@@ -163,6 +164,14 @@ void PythonState::operator<<(const PyThreadState *const tstate) noexcept
163164
Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new
164165
// reference.
165166
this->_top_frame.steal(frame);
167+
#if GREENLET_PY314
168+
if (this->top_frame()) {
169+
this->stackpointer = this->_top_frame->f_frame->stackpointer;
170+
}
171+
else {
172+
this->stackpointer = nullptr;
173+
}
174+
#endif
166175
#if GREENLET_PY313
167176
this->delete_later = Py_XNewRef(tstate->delete_later);
168177
#elif GREENLET_PY312
@@ -241,6 +250,11 @@ void PythonState::operator>>(PyThreadState *const tstate) noexcept
241250
tstate->datastack_chunk = this->datastack_chunk;
242251
tstate->datastack_top = this->datastack_top;
243252
tstate->datastack_limit = this->datastack_limit;
253+
#if GREENLET_PY314 && defined(Py_GIL_DISABLED)
254+
if (this->top_frame()) {
255+
this->_top_frame->f_frame->stackpointer = this->stackpointer;
256+
}
257+
#endif
244258
this->_top_frame.relinquish_ownership();
245259
#if GREENLET_PY313
246260
Py_XDECREF(tstate->delete_later);
@@ -295,6 +309,16 @@ int PythonState::tp_traverse(visitproc visit, void* arg, bool own_top_frame) noe
295309
if (own_top_frame) {
296310
Py_VISIT(this->_top_frame.borrow());
297311
}
312+
#if GREENLET_PY314
313+
// TODO: Should we be visiting the c_stack_refs objects?
314+
// CPython uses a specific macro to do that which takes into
315+
// account boxing and null values and then calls
316+
// ``_PyGC_VisitStackRef``, but we don't have access to that, and
317+
// we can't duplicate it ourself (because it compares
318+
// ``visitproc`` to another function we can't access).
319+
// The naive way of looping over c_stack_refs->ref and visiting
320+
// those crashes the process (at least with GIL disabled).
321+
#endif
298322
return 0;
299323
}
300324

src/greenlet/TThreadState.hpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,13 @@ namespace greenlet {
6666
* invoke destructors; the C++11 version is the most portable solution
6767
* I found). When the thread exits, we can drop references and
6868
* otherwise manipulate greenlets and frames that we know can no
69-
* longer be switched to. For compilers that don't support C++11
70-
* thread locals, we have a solution that uses the python thread
71-
* dictionary, though it may not collect everything as promptly as
72-
* other compilers do, if some other library is using the thread
73-
* dictionary and has a cycle or extra reference.
69+
* longer be switched to.
7470
*
7571
* There are two small wrinkles. The first is that when the thread
7672
* exits, it is too late to actually invoke Python APIs: the Python
7773
* thread state is gone, and the GIL is released. To solve *this*
7874
* problem, our destructor uses ``Py_AddPendingCall`` to transfer the
79-
* destruction work to the main thread. (This is not an issue for the
80-
* dictionary solution.)
75+
* destruction work to the main thread.
8176
*
8277
* The second is that once the thread exits, the thread local object
8378
* is invalid and we can't even access a pointer to it, so we can't

src/greenlet/TThreadStateCreator.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace greenlet {
1515

1616
typedef void (*ThreadStateDestructor)(ThreadState* const);
1717

18+
// Only one of these, auto created per thread as a thread_local.
19+
// Constructing the state constructs the MainGreenlet.
1820
template<ThreadStateDestructor Destructor>
1921
class ThreadStateCreator
2022
{
@@ -36,8 +38,6 @@ class ThreadStateCreator
3638

3739
public:
3840

39-
// Only one of these, auto created per thread.
40-
// Constructing the state constructs the MainGreenlet.
4141
ThreadStateCreator() :
4242
_state((ThreadState*)1)
4343
{

src/greenlet/TThreadStateDestroy.cpp

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct ThreadState_DestroyNoGIL
3333
MarkGreenletDeadAndQueueCleanup(ThreadState* const state)
3434
{
3535
#if GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK
36+
// One rare platform.
3637
return;
3738
#endif
3839
// We are *NOT* holding the GIL. Our thread is in the middle
@@ -70,7 +71,11 @@ struct ThreadState_DestroyNoGIL
7071
static bool
7172
MarkGreenletDeadIfNeeded(ThreadState* const state)
7273
{
73-
if (state && state->has_main_greenlet()) {
74+
if (!state) {
75+
return false;
76+
}
77+
LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
78+
if (state->has_main_greenlet()) {
7479
// mark the thread as dead ASAP.
7580
// this is racy! If we try to throw or switch to a
7681
// greenlet from this thread from some other thread before
@@ -114,7 +119,7 @@ struct ThreadState_DestroyNoGIL
114119
// from the queue; its next iteration will go ahead and delete the item we just added.
115120
// And the pending call we schedule here will have no work to do.
116121
int result = AddPendingCall(
117-
PendingCallback_DestroyQueueWithGIL,
122+
PendingCallback_DestroyQueue,
118123
nullptr);
119124
if (result < 0) {
120125
// Hmm, what can we do here?
@@ -126,10 +131,11 @@ struct ThreadState_DestroyNoGIL
126131
}
127132

128133
static int
129-
PendingCallback_DestroyQueueWithGIL(void* UNUSED(arg))
134+
PendingCallback_DestroyQueue(void* UNUSED(arg))
130135
{
131-
// We're holding the GIL here, so no Python code should be able to
132-
// run to call ``os.fork()``.
136+
// We're may or may not be holding the GIL here (depending on
137+
// Py_GIL_DISABLED), so calls to ``os.fork()`` may or may not
138+
// be possible.
133139
while (1) {
134140
ThreadState* to_destroy;
135141
{
@@ -144,15 +150,15 @@ struct ThreadState_DestroyNoGIL
144150
// Drop the lock while we do the actual deletion.
145151
// This allows other calls to MarkGreenletDeadAndQueueCleanup
146152
// to enter and add to our queue.
147-
DestroyOneWithGIL(to_destroy);
153+
DestroyOne(to_destroy);
148154
}
149155
return 0;
150156
}
151157

152158
static void
153-
DestroyOneWithGIL(const ThreadState* const state)
159+
DestroyOne(const ThreadState* const state)
154160
{
155-
// Holding the GIL.
161+
// May or may not be holding the GIL (depending on Py_GIL_DISABLED).
156162
// Passed a non-shared pointer to the actual thread state.
157163
// state -> main greenlet
158164
assert(state->has_main_greenlet());

src/greenlet/tests/test_gc.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -84,26 +84,3 @@ def greenlet_body():
8484
del g
8585
greenlet.getcurrent()
8686
gc.collect()
87-
88-
# def test_crashing_cycle(self):
89-
# # CPython 3.14 free threaded crashes on this
90-
# # (from docs/greenlet_gc.rst)
91-
# import doctest
92-
93-
# def with_doctest():
94-
# """
95-
# >>> import gc
96-
# >>> from greenlet import getcurrent, greenlet, GreenletExit
97-
# >>> def collect_it():
98-
# ... print("Collecting garbage")
99-
# ... gc.collect()
100-
# >>> def outer():
101-
# ... gc.collect()
102-
# >>> outer_glet = greenlet(outer)
103-
# >>> outer_glet.switch()
104-
# Collecting garbage
105-
# >>> print(list(globals()))
106-
# >>> print(list(locals()))
107-
# """
108-
109-
# doctest.run_docstring_examples(with_doctest, dict())

0 commit comments

Comments
 (0)