123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- #ifndef GREENLET_CPYTHON_ADD_PENDING_HPP
- #define GREENLET_CPYTHON_ADD_PENDING_HPP
- #if (PY_VERSION_HEX >= 0x30800A0 && PY_VERSION_HEX < 0x3090000) && !(defined(_WIN32) || defined(WIN32))
- // XXX: From Python 3.8a3 [1] up until Python 3.9a6 [2][3],
- // ``Py_AddPendingCall`` would try to produce a Python exception if
- // the interpreter was in the beginning of shutting down when this
- // function is called. However, ``Py_AddPendingCall`` doesn't require
- // the GIL, and we are absolutely not holding it when we make that
- // call. That means that trying to create the Python exception is
- // using the C API in an undefined state; here the C API detects this
- // and aborts the process with an error ("Fatal Python error: Python
- // memory allocator called without holding the GIL": Add ->
- // PyErr_SetString -> PyUnicode_New -> PyObject_Malloc). This arises
- // (obviously) in multi-threaded programs and happens if one thread is
- // exiting and cleaning up its thread-local data while the other
- // thread is trying to shut down the interpreter. A crash on shutdown
- // is still a crash and could result in data loss (e.g., daemon
- // threads are still running, pending signal handlers may be present,
- // buffers may not be flushed, there may be __del__ that need run,
- // etc), so we have to work around it.
- //
- // Of course, we can (and do) check for whether the interpreter is
- // shutting down before calling ``Py_AddPendingCall``, but that's a
- // race condition since we don't hold the GIL, and so we may not
- // actually get the right answer. Plus, ``Py_FinalizeEx`` actually
- // calls ``_Py_FinishPendingCalls`` (which sets the pending->finishing
- // flag, which is used to gate creating the exceptioen) *before*
- // publishing any other data that would let us detect the shutdown
- // (such as runtime->finalizing). So that point is moot.
- //
- // Our solution for those versions is to inline the same code, without
- // the problematic bit that sets the exception. Unfortunately, all of
- // the structure definitions are private/opaque, *and* we can't
- // actually count on being able to include their definitions from
- // ``internal/pycore_*``, because on some platforms those header files
- // are incomplete (i.e., on macOS with macports 3.8, the includes are
- // fine, but on Ubuntu jammy with 3.8 from ppa:deadsnakes or GitHub
- // Actions 3.8 (I think it's Ubuntu 18.04), they con't be used; at
- // least, I couldn't get them to work). So we need to define the
- // structures and _PyRuntime data member ourself. Yet more
- // unfortunately, _PyRuntime won't link on Windows, so we can only do
- // this on other platforms.
- //
- // [1] https://github.com/python/cpython/commit/842a2f07f2f08a935ef470bfdaeef40f87490cfc
- // [2] https://github.com/python/cpython/commit/cfc3c2f8b34d3864717ab584c5b6c260014ba55a
- // [3] https://github.com/python/cpython/issues/81308
- # define GREENLET_BROKEN_PY_ADD_PENDING 1
- // When defining these structures, the important thing is to get
- // binary compatibility, i.e., structure layout. For that, we only
- // need to define fields up to the ones we use; after that they're
- // irrelevant UNLESS the structure is included in another structure
- // *before* the structure we're interested in --- in that case, it
- // must be complete. Ellipsis indicate elided trailing members.
- // Pointer types are changed to void* to keep from having to define
- // more structures.
- // From "internal/pycore_atomic.h"
- // There are several different definitions of this, including the
- // plain ``int`` version, a ``volatile int`` and an ``_Atomic int``
- // I don't think any of those change the size/layout.
- typedef struct _Py_atomic_int {
- volatile int _value;
- } _Py_atomic_int;
- // This needs too much infrastructure, so we just do a regular store.
- #define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL) \
- (ATOMIC_VAL)->_value = NEW_VAL
- // From "internal/pycore_pymem.h"
- #define NUM_GENERATIONS 3
- struct gc_generation {
- PyGC_Head head; // We already have this defined.
- int threshold;
- int count;
- };
- struct gc_generation_stats {
- Py_ssize_t collections;
- Py_ssize_t collected;
- Py_ssize_t uncollectable;
- };
- struct _gc_runtime_state {
- void *trash_delete_later;
- int trash_delete_nesting;
- int enabled;
- int debug;
- struct gc_generation generations[NUM_GENERATIONS];
- void *generation0;
- struct gc_generation permanent_generation;
- struct gc_generation_stats generation_stats[NUM_GENERATIONS];
- int collecting;
- void *garbage;
- void *callbacks;
- Py_ssize_t long_lived_total;
- Py_ssize_t long_lived_pending;
- };
- // From "internal/pycore_pystate.h"
- struct _pending_calls {
- int finishing;
- PyThread_type_lock lock;
- _Py_atomic_int calls_to_do;
- int async_exc;
- #define NPENDINGCALLS 32
- struct {
- int (*func)(void *);
- void *arg;
- } calls[NPENDINGCALLS];
- int first;
- int last;
- };
- struct _ceval_runtime_state {
- int recursion_limit;
- int tracing_possible;
- _Py_atomic_int eval_breaker;
- _Py_atomic_int gil_drop_request;
- struct _pending_calls pending;
- // ...
- };
- typedef struct pyruntimestate {
- int preinitializing;
- int preinitialized;
- int core_initialized;
- int initialized;
- void *finalizing;
- struct pyinterpreters {
- PyThread_type_lock mutex;
- void *head;
- void *main;
- int64_t next_id;
- } interpreters;
- // XXX Remove this field once we have a tp_* slot.
- struct _xidregistry {
- PyThread_type_lock mutex;
- void *head;
- } xidregistry;
- unsigned long main_thread;
- #define NEXITFUNCS 32
- void (*exitfuncs[NEXITFUNCS])(void);
- int nexitfuncs;
- struct _gc_runtime_state gc;
- struct _ceval_runtime_state ceval;
- // ...
- } _PyRuntimeState;
- #define SIGNAL_PENDING_CALLS(ceval) \
- do { \
- _Py_atomic_store_relaxed(&(ceval)->pending.calls_to_do, 1); \
- _Py_atomic_store_relaxed(&(ceval)->eval_breaker, 1); \
- } while (0)
- extern _PyRuntimeState _PyRuntime;
- #else
- # define GREENLET_BROKEN_PY_ADD_PENDING 0
- #endif
- #endif
|