You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
372 lines
12 KiB
372 lines
12 KiB
#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);
|
|
}
|