123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- #ifndef WITH_THREAD
- # error "xxx no-thread configuration not tested, please report if you need that"
- #endif
- #include "pythread.h"
- struct cffi_tls_s {
- /* The current thread's ThreadCanaryObj. This is only non-null in
- case cffi builds the thread state here. It remains null if this
- thread had already a thread state provided by CPython. */
- struct thread_canary_s *local_thread_canary;
- #ifndef USE__THREAD
- /* The saved errno. If the C compiler supports '__thread', then
- we use that instead. */
- int saved_errno;
- #endif
- #ifdef MS_WIN32
- /* The saved lasterror, on Windows. */
- int saved_lasterror;
- #endif
- };
- static struct cffi_tls_s *get_cffi_tls(void); /* in misc_thread_posix.h
- or misc_win32.h */
- /* We try to keep the PyThreadState around in a thread not started by
- * Python but where cffi callbacks occur. If we didn't do that, then
- * the standard logic in PyGILState_Ensure() and PyGILState_Release()
- * would create a new PyThreadState and completely free it for every
- * single call. For some applications, this is a huge slow-down.
- *
- * As shown by issue #362, it is quite messy to do. The current
- * solution is to keep the PyThreadState alive by incrementing its
- * 'gilstate_counter'. We detect thread shut-down, and we put the
- * PyThreadState inside a list of zombies (we can't free it
- * immediately because we don't have the GIL at that point in time).
- * We also detect other pieces of code (notably Py_Finalize()) which
- * clear and free PyThreadStates under our feet, using ThreadCanaryObj.
- */
- #define TLS_ZOM_LOCK() PyThread_acquire_lock(cffi_zombie_lock, WAIT_LOCK)
- #define TLS_ZOM_UNLOCK() PyThread_release_lock(cffi_zombie_lock)
- static PyThread_type_lock cffi_zombie_lock = NULL;
- /* A 'canary' object is created in a thread when there is a callback
- invoked, and that thread has no PyThreadState so far. It is an
- object of reference count equal to 1, which is stored in the
- PyThreadState->dict. Two things can occur then:
- 1. The PyThreadState can be forcefully cleared by Py_Finalize().
- Then thread_canary_dealloc() is called, and we have to cancel
- the hacks we did to keep the PyThreadState alive.
- 2. The thread finishes. In that case, we put the canary in a list
- of zombies, and at some convenient time later when we have the
- GIL, we free all PyThreadStates in the zombie list.
- Some more fun comes from the fact that thread_canary_dealloc() can
- be called at a point where the canary is in the zombie list already.
- Also, the various pieces are freed at specific points in time, and
- we must make sure not to access already-freed structures:
- - the struct cffi_tls_s is valid until the thread shuts down, and
- then it is freed by cffi_thread_shutdown().
- - the canary is a normal Python object, but we have a borrowed
- reference to it from cffi_tls_s.local_thread_canary.
- */
- typedef struct thread_canary_s {
- PyObject_HEAD
- struct thread_canary_s *zombie_prev, *zombie_next;
- PyThreadState *tstate;
- struct cffi_tls_s *tls;
- } ThreadCanaryObj;
- static PyTypeObject ThreadCanary_Type; /* forward */
- static ThreadCanaryObj cffi_zombie_head;
- static void
- _thread_canary_detach_with_lock(ThreadCanaryObj *ob)
- {
- /* must be called with both the GIL and TLS_ZOM_LOCK. */
- ThreadCanaryObj *p, *n;
- p = ob->zombie_prev;
- n = ob->zombie_next;
- p->zombie_next = n;
- n->zombie_prev = p;
- ob->zombie_prev = NULL;
- ob->zombie_next = NULL;
- }
- static void
- thread_canary_dealloc(ThreadCanaryObj *ob)
- {
- /* this ThreadCanaryObj is being freed: if it is in the zombie
- chained list, remove it. Thread-safety: 'zombie_next' amd
- 'local_thread_canary' accesses need to be protected with
- the TLS_ZOM_LOCK.
- */
- TLS_ZOM_LOCK();
- if (ob->zombie_next != NULL) {
- //fprintf(stderr, "thread_canary_dealloc(%p): ZOMBIE\n", ob);
- _thread_canary_detach_with_lock(ob);
- }
- else {
- //fprintf(stderr, "thread_canary_dealloc(%p): not a zombie\n", ob);
- }
- if (ob->tls != NULL) {
- //fprintf(stderr, "thread_canary_dealloc(%p): was local_thread_canary\n", ob);
- assert(ob->tls->local_thread_canary == ob);
- ob->tls->local_thread_canary = NULL;
- }
- TLS_ZOM_UNLOCK();
- PyObject_Del((PyObject *)ob);
- }
- static void
- thread_canary_make_zombie(ThreadCanaryObj *ob)
- {
- /* This must be called without the GIL, but with the TLS_ZOM_LOCK.
- It must be called at most once for a given ThreadCanaryObj. */
- ThreadCanaryObj *last;
- //fprintf(stderr, "thread_canary_make_zombie(%p)\n", ob);
- if (ob->zombie_next)
- Py_FatalError("cffi: ThreadCanaryObj is already a zombie");
- last = cffi_zombie_head.zombie_prev;
- ob->zombie_next = &cffi_zombie_head;
- ob->zombie_prev = last;
- last->zombie_next = ob;
- cffi_zombie_head.zombie_prev = ob;
- }
- static void
- thread_canary_free_zombies(void)
- {
- /* This must be called with the GIL. */
- if (cffi_zombie_head.zombie_next == &cffi_zombie_head)
- return; /* fast path */
- while (1) {
- ThreadCanaryObj *ob;
- PyThreadState *tstate = NULL;
- TLS_ZOM_LOCK();
- ob = cffi_zombie_head.zombie_next;
- if (ob != &cffi_zombie_head) {
- tstate = ob->tstate;
- //fprintf(stderr, "thread_canary_free_zombie(%p) tstate=%p\n", ob, tstate);
- _thread_canary_detach_with_lock(ob);
- if (tstate == NULL)
- Py_FatalError("cffi: invalid ThreadCanaryObj->tstate");
- }
- TLS_ZOM_UNLOCK();
- if (tstate == NULL)
- break;
- PyThreadState_Clear(tstate); /* calls thread_canary_dealloc on 'ob',
- but now ob->zombie_next == NULL. */
- PyThreadState_Delete(tstate);
- //fprintf(stderr, "thread_canary_free_zombie: cleared and deleted tstate=%p\n", tstate);
- }
- //fprintf(stderr, "thread_canary_free_zombie: end\n");
- }
- static void
- thread_canary_register(PyThreadState *tstate)
- {
- /* called with the GIL; 'tstate' is the current PyThreadState. */
- ThreadCanaryObj *canary;
- PyObject *tdict;
- struct cffi_tls_s *tls;
- int err;
- /* first free the zombies, if any */
- thread_canary_free_zombies();
- tls = get_cffi_tls();
- if (tls == NULL)
- goto ignore_error;
- tdict = PyThreadState_GetDict();
- if (tdict == NULL)
- goto ignore_error;
- canary = PyObject_New(ThreadCanaryObj, &ThreadCanary_Type);
- //fprintf(stderr, "thread_canary_register(%p): tstate=%p tls=%p\n", canary, tstate, tls);
- if (canary == NULL)
- goto ignore_error;
- canary->zombie_prev = NULL;
- canary->zombie_next = NULL;
- canary->tstate = tstate;
- canary->tls = tls;
- err = PyDict_SetItemString(tdict, "cffi.thread.canary", (PyObject *)canary);
- Py_DECREF(canary);
- if (err < 0)
- goto ignore_error;
- /* thread-safety: we have the GIL here, and 'tstate' is the one that
- corresponds to our own thread. We are allocating a new 'canary'
- and setting it up for our own thread, both in 'tdict' (which owns
- the reference) and in 'tls->local_thread_canary' (which doesn't). */
- assert(Py_REFCNT(canary) == 1);
- tls->local_thread_canary = canary;
- tstate->gilstate_counter++;
- /* ^^^ this means 'tstate' will never be automatically freed by
- PyGILState_Release() */
- return;
- ignore_error:
- PyErr_Clear();
- }
- static PyTypeObject ThreadCanary_Type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "_cffi_backend.thread_canary",
- sizeof(ThreadCanaryObj),
- 0,
- (destructor)thread_canary_dealloc, /* tp_dealloc */
- 0, /* tp_print */
- 0, /* tp_getattr */
- 0, /* tp_setattr */
- 0, /* tp_compare */
- 0, /* tp_repr */
- 0, /* tp_as_number */
- 0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
- 0, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
- 0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT, /* tp_flags */
- };
- static void init_cffi_tls_zombie(void)
- {
- cffi_zombie_head.zombie_next = &cffi_zombie_head;
- cffi_zombie_head.zombie_prev = &cffi_zombie_head;
- cffi_zombie_lock = PyThread_allocate_lock();
- if (cffi_zombie_lock == NULL)
- PyErr_SetString(PyExc_SystemError, "can't allocate cffi_zombie_lock");
- }
- static void cffi_thread_shutdown(void *p)
- {
- /* this function is called from misc_thread_posix or misc_win32
- when a thread is about to end. */
- struct cffi_tls_s *tls = (struct cffi_tls_s *)p;
- /* thread-safety: this field 'local_thread_canary' can be reset
- to NULL in parallel, protected by TLS_ZOM_LOCK. */
- TLS_ZOM_LOCK();
- if (tls->local_thread_canary != NULL) {
- tls->local_thread_canary->tls = NULL;
- thread_canary_make_zombie(tls->local_thread_canary);
- }
- TLS_ZOM_UNLOCK();
- //fprintf(stderr, "thread_shutdown(%p)\n", tls);
- free(tls);
- }
- /* USE__THREAD is defined by setup.py if it finds that it is
- syntactically valid to use "__thread" with this C compiler. */
- #ifdef USE__THREAD
- static __thread int cffi_saved_errno = 0;
- static void save_errno_only(void) { cffi_saved_errno = errno; }
- static void restore_errno_only(void) { errno = cffi_saved_errno; }
- #else
- static void save_errno_only(void)
- {
- int saved = errno;
- struct cffi_tls_s *tls = get_cffi_tls();
- if (tls != NULL)
- tls->saved_errno = saved;
- }
- static void restore_errno_only(void)
- {
- struct cffi_tls_s *tls = get_cffi_tls();
- if (tls != NULL)
- errno = tls->saved_errno;
- }
- #endif
- /* MESS. We can't use PyThreadState_GET(), because that calls
- PyThreadState_Get() which fails an assert if the result is NULL.
-
- * in Python 2.7 and <= 3.4, the variable _PyThreadState_Current
- is directly available, so use that.
- * in Python 3.5, the variable is available too, but it might be
- the case that the headers don't define it (this changed in 3.5.1).
- In case we're compiling with 3.5.x with x >= 1, we need to
- manually define this variable.
- * in Python >= 3.6 there is _PyThreadState_UncheckedGet().
- It was added in 3.5.2 but should never be used in 3.5.x
- because it is not available in 3.5.0 or 3.5.1.
- */
- #if PY_VERSION_HEX >= 0x03050100 && PY_VERSION_HEX < 0x03060000
- PyAPI_DATA(void *volatile) _PyThreadState_Current;
- #endif
- static PyThreadState *get_current_ts(void)
- {
- #if PY_VERSION_HEX >= 0x03060000
- return _PyThreadState_UncheckedGet();
- #elif defined(_Py_atomic_load_relaxed)
- return (PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current);
- #else
- return (PyThreadState*)_PyThreadState_Current; /* assume atomic read */
- #endif
- }
- static PyGILState_STATE gil_ensure(void)
- {
- /* Called at the start of a callback. Replacement for
- PyGILState_Ensure().
- */
- PyGILState_STATE result;
- PyThreadState *ts = PyGILState_GetThisThreadState();
- if (ts != NULL) {
- ts->gilstate_counter++;
- if (ts != get_current_ts()) {
- /* common case: 'ts' is our non-current thread state and
- we have to make it current and acquire the GIL */
- PyEval_RestoreThread(ts);
- return PyGILState_UNLOCKED;
- }
- else {
- return PyGILState_LOCKED;
- }
- }
- else {
- /* no thread state here so far. */
- result = PyGILState_Ensure();
- assert(result == PyGILState_UNLOCKED);
- ts = PyGILState_GetThisThreadState();
- assert(ts != NULL);
- assert(ts == get_current_ts());
- assert(ts->gilstate_counter >= 1);
- /* Use the ThreadCanary mechanism to keep 'ts' alive until the
- thread really shuts down */
- thread_canary_register(ts);
- return result;
- }
- }
- static void gil_release(PyGILState_STATE oldstate)
- {
- PyGILState_Release(oldstate);
- }
|