| #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); |
| } |