11#ifndef GREENLET_THREAD_STATE_HPP
22#define GREENLET_THREAD_STATE_HPP
33
4+ #include < cstdlib>
45#include < ctime>
56#include < stdexcept>
67
@@ -22,6 +23,13 @@ using greenlet::refs::CreatedModule;
2223using greenlet::refs::PyErrPieces;
2324using greenlet::refs::NewReference;
2425
26+ // Defined in PyModule.cpp; set by an atexit handler to signal
27+ // that the interpreter is shutting down. Only needed on
28+ // Python < 3.11 where _Py_IsFinalizing() is set too late.
29+ #if !GREENLET_PY311
30+ extern int g_greenlet_shutting_down;
31+ #endif
32+
2533namespace greenlet {
2634/* *
2735 * Thread-local state of greenlets.
@@ -104,7 +112,13 @@ class ThreadState {
104112 /* Strong reference to the trace function, if any. */
105113 OwnedObject tracefunc;
106114
107- typedef std::vector<PyGreenlet*, PythonAllocator<PyGreenlet*> > deleteme_t ;
115+ // Use std::allocator (malloc/free) instead of PythonAllocator
116+ // (PyMem_Malloc) for the deleteme list. During Py_FinalizeEx on
117+ // Python < 3.11, the PyObject_Malloc pool that holds ThreadState
118+ // can be disrupted, corrupting any PythonAllocator-backed
119+ // containers. Using std::allocator makes this vector independent
120+ // of Python's allocator lifecycle.
121+ typedef std::vector<PyGreenlet*> deleteme_t ;
108122 /* A vector of raw PyGreenlet pointers representing things that need
109123 deleted when this thread is running. The vector owns the
110124 references, but you need to manually INCREF/DECREF as you use
@@ -120,7 +134,6 @@ class ThreadState {
120134
121135 static std::clock_t _clocks_used_doing_gc;
122136 static ImmortalString get_referrers_name;
123- static PythonAllocator<ThreadState> allocator;
124137
125138 G_NO_COPIES_OF_CLS (ThreadState);
126139
@@ -146,15 +159,21 @@ class ThreadState {
146159
147160
148161public:
149- static void * operator new (size_t UNUSED (count))
162+ // Allocate ThreadState with malloc/free rather than Python's object
163+ // allocator. ThreadState outlives many Python objects and must
164+ // remain valid throughout Py_FinalizeEx. On Python < 3.11,
165+ // PyObject_Malloc pools can be disrupted during early finalization,
166+ // corrupting any C++ objects stored in them.
167+ static void * operator new (size_t count)
150168 {
151- return ThreadState::allocator.allocate (1 );
169+ void * p = std::malloc (count);
170+ if (!p) throw std::bad_alloc ();
171+ return p;
152172 }
153173
154174 static void operator delete (void * ptr)
155175 {
156- return ThreadState::allocator.deallocate (static_cast <ThreadState*>(ptr),
157- 1 );
176+ std::free (ptr);
158177 }
159178
160179 static void init ()
@@ -283,33 +302,50 @@ class ThreadState {
283302 inline void clear_deleteme_list (const bool murder=false )
284303 {
285304 if (!this ->deleteme .empty ()) {
286- // It's possible we could add items to this list while
287- // running Python code if there's a thread switch, so we
288- // need to defensively copy it before that can happen.
289- deleteme_t copy = this ->deleteme ;
290- this ->deleteme .clear (); // in case things come back on the list
305+ // Move the list contents out with swap — a constant-time
306+ // pointer exchange that never allocates. The previous code
307+ // used a copy (deleteme_t copy = this->deleteme) which
308+ // allocated through PythonAllocator / PyMem_Malloc; that
309+ // could SIGSEGV during early Py_FinalizeEx on Python < 3.11
310+ // when the allocator is partially torn down.
311+ deleteme_t copy;
312+ std::swap (copy, this ->deleteme );
313+
314+ // During Py_FinalizeEx cleanup, the GC or atexit handlers
315+ // may have already collected objects in this list, leaving
316+ // dangling pointers. Attempting Py_DECREF on freed memory
317+ // causes a SIGSEGV. On Python < 3.11,
318+ // g_greenlet_shutting_down covers the early stages
319+ // (before Py_IsFinalizing() is set).
320+ #if !GREENLET_PY311
321+ if (g_greenlet_shutting_down || Py_IsFinalizing ()) {
322+ return ;
323+ }
324+ #else
325+ if (Py_IsFinalizing ()) {
326+ return ;
327+ }
328+ #endif
329+
330+ // Preserve any pending exception so that cleanup-triggered
331+ // errors don't accidentally swallow an unrelated exception
332+ // (e.g. one set by throw() before a switch).
333+ PyErrPieces incoming_err;
334+
291335 for (deleteme_t ::iterator it = copy.begin (), end = copy.end ();
292336 it != end;
293337 ++it ) {
294338 PyGreenlet* to_del = *it;
295339 if (murder) {
296- // Force each greenlet to appear dead; we can't raise an
297- // exception into it anymore anyway.
298340 to_del->pimpl ->murder_in_place ();
299341 }
300-
301- // The only reference to these greenlets should be in
302- // this list, decreffing them should let them be
303- // deleted again, triggering calls to green_dealloc()
304- // in the correct thread (if we're not murdering).
305- // This may run arbitrary Python code and switch
306- // threads or greenlets!
307342 Py_DECREF (to_del);
308343 if (PyErr_Occurred ()) {
309344 PyErr_WriteUnraisable (nullptr );
310345 PyErr_Clear ();
311346 }
312347 }
348+ incoming_err.PyErrRestore ();
313349 }
314350 }
315351
@@ -371,7 +407,7 @@ class ThreadState {
371407 // Python 3.11+ restructured interpreter finalization so that
372408 // these APIs remain safe during shutdown.
373409#if !GREENLET_PY311
374- if (_Py_IsFinalizing ()) {
410+ if (Py_IsFinalizing ()) {
375411 this ->tracefunc .CLEAR ();
376412 if (this ->current_greenlet ) {
377413 this ->current_greenlet ->murder_in_place ();
@@ -505,7 +541,6 @@ class ThreadState {
505541};
506542
507543ImmortalString ThreadState::get_referrers_name (nullptr );
508- PythonAllocator<ThreadState> ThreadState::allocator;
509544std::clock_t ThreadState::_clocks_used_doing_gc (0 );
510545
511546
0 commit comments