@@ -27,25 +27,52 @@ using std::endl;
2727namespace greenlet
2828{
2929 class Greenlet ;
30- // _Py_IsFinalizing() is only set AFTER atexit handlers complete
31- // inside Py_FinalizeEx on ALL Python versions (including 3.11+).
32- // Code running in atexit handlers (e.g. uWSGI plugin cleanup
33- // calling Py_FinalizeEx, New Relic agent shutdown) can still call
34- // greenlet.getcurrent(), but by that time type objects or
35- // internal state may have been invalidated. This flag is set by
36- // an atexit handler registered at module init (LIFO = runs
37- // first).
38- //
39- // Because this is only set from an atexit handler, by which point
40- // we're single threaded, there should be no need to make it
41- // std::atomic<int>.
42- // TODO: Move this to the GreenletGlobals object?
43- static int g_greenlet_shutting_down;
4430
4531 static inline bool
4632 IsShuttingDown ()
4733 {
48- return greenlet::g_greenlet_shutting_down || Py_IsFinalizing ();
34+ // This used to check a flag set by an ``atexit`` callback.
35+ // This was wrong: the interpreter is still fully functional
36+ // while *all* atexit callbacks are run, and it is perfectly
37+ // valid for an atexit callback that runs after our atexit
38+ // callback (i.e., registered first/before ours) to want to
39+ // make use of greenlet services --- this comes up easily with
40+ // gevent monkey-patching. Almost immediately after atexit callbacks,
41+ // and before any destructive action is taken, Python arranges
42+ // for Py_IsFinalizing to become true.
43+
44+ // It may see me could potentially tighten this check even more (and
45+ // eliminate a function call) by setting a flag in a
46+ // destructor function for our PyCapsule object (_C_API) to
47+ // determine when we're shutting down. ``Py_IsFinalizing``
48+ // becomes true relatively early in the shutdown process,
49+ // while Capsule destructor functions only run when the module
50+ // has actually been torn down --- well, when all of its dicts are
51+ // cleared and collected; recall that because we use
52+ // single-phase init, there is a "hidden" copy of the module
53+ // dict kept by CPython internals used to re-populate a module
54+ // if greenlet is imported twice, so Python code can't trigger
55+ // C_API to get GC'd early without seriously poking at CPython
56+ // internals, e.g., by using `gc.get_referrers` to find the
57+ // hidden dict. However, C extensions could have INCREF the
58+ // capsule object and prevent it from *ever* getting torn
59+ // down, so this isn't reliable.
60+
61+ // We could probably be even "smarter" and replace values in
62+ // _PyGreenlet_API with different values at destruction time.
63+ // For the PyObject* returning APIs, we could replace them
64+ // with versions that set an exception and return null --- the
65+ // benefit being that we don't have to include a
66+ // Py_IsFinalizing() call in the normal path; int returning
67+ // APIs would be handled on a case-by-case basis; unclear what
68+ // to do with the types. This is of questionable benefit
69+ // though because by the time our destructor is called, our
70+ // module is about to be destroyed which may take our
71+ // allocated storage with it (if CPython ever dynamically
72+ // unloads loaded shared libraries, which as of 3.14 it never
73+ // does).
74+
75+ return Py_IsFinalizing ();
4976 }
5077
5178 namespace refs
0 commit comments