• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #ifndef WITH_THREAD
2 # error "xxx no-thread configuration not tested, please report if you need that"
3 #endif
4 #include "pythread.h"
5 
6 
7 struct cffi_tls_s {
8     /* The current thread's ThreadCanaryObj.  This is only non-null in
9        case cffi builds the thread state here.  It remains null if this
10        thread had already a thread state provided by CPython. */
11     struct thread_canary_s *local_thread_canary;
12 
13 #ifndef USE__THREAD
14     /* The saved errno.  If the C compiler supports '__thread', then
15        we use that instead. */
16     int saved_errno;
17 #endif
18 
19 #ifdef MS_WIN32
20     /* The saved lasterror, on Windows. */
21     int saved_lasterror;
22 #endif
23 };
24 
25 static struct cffi_tls_s *get_cffi_tls(void);   /* in misc_thread_posix.h
26                                                    or misc_win32.h */
27 
28 
29 /* We try to keep the PyThreadState around in a thread not started by
30  * Python but where cffi callbacks occur.  If we didn't do that, then
31  * the standard logic in PyGILState_Ensure() and PyGILState_Release()
32  * would create a new PyThreadState and completely free it for every
33  * single call.  For some applications, this is a huge slow-down.
34  *
35  * As shown by issue #362, it is quite messy to do.  The current
36  * solution is to keep the PyThreadState alive by incrementing its
37  * 'gilstate_counter'.  We detect thread shut-down, and we put the
38  * PyThreadState inside a list of zombies (we can't free it
39  * immediately because we don't have the GIL at that point in time).
40  * We also detect other pieces of code (notably Py_Finalize()) which
41  * clear and free PyThreadStates under our feet, using ThreadCanaryObj.
42  */
43 
44 #define TLS_ZOM_LOCK() PyThread_acquire_lock(cffi_zombie_lock, WAIT_LOCK)
45 #define TLS_ZOM_UNLOCK() PyThread_release_lock(cffi_zombie_lock)
46 static PyThread_type_lock cffi_zombie_lock = NULL;
47 
48 
49 /* A 'canary' object is created in a thread when there is a callback
50    invoked, and that thread has no PyThreadState so far.  It is an
51    object of reference count equal to 1, which is stored in the
52    PyThreadState->dict.  Two things can occur then:
53 
54    1. The PyThreadState can be forcefully cleared by Py_Finalize().
55       Then thread_canary_dealloc() is called, and we have to cancel
56       the hacks we did to keep the PyThreadState alive.
57 
58    2. The thread finishes.  In that case, we put the canary in a list
59       of zombies, and at some convenient time later when we have the
60       GIL, we free all PyThreadStates in the zombie list.
61 
62    Some more fun comes from the fact that thread_canary_dealloc() can
63    be called at a point where the canary is in the zombie list already.
64    Also, the various pieces are freed at specific points in time, and
65    we must make sure not to access already-freed structures:
66 
67     - the struct cffi_tls_s is valid until the thread shuts down, and
68       then it is freed by cffi_thread_shutdown().
69 
70     - the canary is a normal Python object, but we have a borrowed
71       reference to it from cffi_tls_s.local_thread_canary.
72  */
73 
74 typedef struct thread_canary_s {
75     PyObject_HEAD
76     struct thread_canary_s *zombie_prev, *zombie_next;
77     PyThreadState *tstate;
78     struct cffi_tls_s *tls;
79 } ThreadCanaryObj;
80 
81 static PyTypeObject ThreadCanary_Type;    /* forward */
82 static ThreadCanaryObj cffi_zombie_head;
83 
84 static void
_thread_canary_detach_with_lock(ThreadCanaryObj * ob)85 _thread_canary_detach_with_lock(ThreadCanaryObj *ob)
86 {
87     /* must be called with both the GIL and TLS_ZOM_LOCK. */
88     ThreadCanaryObj *p, *n;
89     p = ob->zombie_prev;
90     n = ob->zombie_next;
91     p->zombie_next = n;
92     n->zombie_prev = p;
93     ob->zombie_prev = NULL;
94     ob->zombie_next = NULL;
95 }
96 
97 static void
thread_canary_dealloc(ThreadCanaryObj * ob)98 thread_canary_dealloc(ThreadCanaryObj *ob)
99 {
100     /* this ThreadCanaryObj is being freed: if it is in the zombie
101        chained list, remove it.  Thread-safety: 'zombie_next' amd
102        'local_thread_canary' accesses need to be protected with
103        the TLS_ZOM_LOCK.
104      */
105     TLS_ZOM_LOCK();
106     if (ob->zombie_next != NULL) {
107         //fprintf(stderr, "thread_canary_dealloc(%p): ZOMBIE\n", ob);
108         _thread_canary_detach_with_lock(ob);
109     }
110     else {
111         //fprintf(stderr, "thread_canary_dealloc(%p): not a zombie\n", ob);
112     }
113 
114     if (ob->tls != NULL) {
115         //fprintf(stderr, "thread_canary_dealloc(%p): was local_thread_canary\n", ob);
116         assert(ob->tls->local_thread_canary == ob);
117         ob->tls->local_thread_canary = NULL;
118     }
119     TLS_ZOM_UNLOCK();
120 
121     PyObject_Del((PyObject *)ob);
122 }
123 
124 static void
thread_canary_make_zombie(ThreadCanaryObj * ob)125 thread_canary_make_zombie(ThreadCanaryObj *ob)
126 {
127     /* This must be called without the GIL, but with the TLS_ZOM_LOCK.
128        It must be called at most once for a given ThreadCanaryObj. */
129     ThreadCanaryObj *last;
130 
131     //fprintf(stderr, "thread_canary_make_zombie(%p)\n", ob);
132     if (ob->zombie_next)
133         Py_FatalError("cffi: ThreadCanaryObj is already a zombie");
134     last = cffi_zombie_head.zombie_prev;
135     ob->zombie_next = &cffi_zombie_head;
136     ob->zombie_prev = last;
137     last->zombie_next = ob;
138     cffi_zombie_head.zombie_prev = ob;
139 }
140 
141 static void
thread_canary_free_zombies(void)142 thread_canary_free_zombies(void)
143 {
144     /* This must be called with the GIL. */
145     if (cffi_zombie_head.zombie_next == &cffi_zombie_head)
146         return;    /* fast path */
147 
148     while (1) {
149         ThreadCanaryObj *ob;
150         PyThreadState *tstate = NULL;
151 
152         TLS_ZOM_LOCK();
153         ob = cffi_zombie_head.zombie_next;
154         if (ob != &cffi_zombie_head) {
155             tstate = ob->tstate;
156             //fprintf(stderr, "thread_canary_free_zombie(%p) tstate=%p\n", ob, tstate);
157             _thread_canary_detach_with_lock(ob);
158             if (tstate == NULL)
159                 Py_FatalError("cffi: invalid ThreadCanaryObj->tstate");
160         }
161         TLS_ZOM_UNLOCK();
162 
163         if (tstate == NULL)
164             break;
165         PyThreadState_Clear(tstate);  /* calls thread_canary_dealloc on 'ob',
166                                          but now ob->zombie_next == NULL. */
167         PyThreadState_Delete(tstate);
168         //fprintf(stderr, "thread_canary_free_zombie: cleared and deleted tstate=%p\n", tstate);
169     }
170     //fprintf(stderr, "thread_canary_free_zombie: end\n");
171 }
172 
173 static void
thread_canary_register(PyThreadState * tstate)174 thread_canary_register(PyThreadState *tstate)
175 {
176     /* called with the GIL; 'tstate' is the current PyThreadState. */
177     ThreadCanaryObj *canary;
178     PyObject *tdict;
179     struct cffi_tls_s *tls;
180     int err;
181 
182     /* first free the zombies, if any */
183     thread_canary_free_zombies();
184 
185     tls = get_cffi_tls();
186     if (tls == NULL)
187         goto ignore_error;
188 
189     tdict = PyThreadState_GetDict();
190     if (tdict == NULL)
191         goto ignore_error;
192 
193     canary = PyObject_New(ThreadCanaryObj, &ThreadCanary_Type);
194     //fprintf(stderr, "thread_canary_register(%p): tstate=%p tls=%p\n", canary, tstate, tls);
195     if (canary == NULL)
196         goto ignore_error;
197     canary->zombie_prev = NULL;
198     canary->zombie_next = NULL;
199     canary->tstate = tstate;
200     canary->tls = tls;
201 
202     err = PyDict_SetItemString(tdict, "cffi.thread.canary", (PyObject *)canary);
203     Py_DECREF(canary);
204     if (err < 0)
205         goto ignore_error;
206 
207     /* thread-safety: we have the GIL here, and 'tstate' is the one that
208        corresponds to our own thread.  We are allocating a new 'canary'
209        and setting it up for our own thread, both in 'tdict' (which owns
210        the reference) and in 'tls->local_thread_canary' (which doesn't). */
211     assert(Py_REFCNT(canary) == 1);
212     tls->local_thread_canary = canary;
213     tstate->gilstate_counter++;
214     /* ^^^ this means 'tstate' will never be automatically freed by
215            PyGILState_Release() */
216     return;
217 
218  ignore_error:
219     PyErr_Clear();
220 }
221 
222 static PyTypeObject ThreadCanary_Type = {
223     PyVarObject_HEAD_INIT(NULL, 0)
224     "_cffi_backend.thread_canary",
225     sizeof(ThreadCanaryObj),
226     0,
227     (destructor)thread_canary_dealloc,          /* tp_dealloc */
228     0,                                          /* tp_print */
229     0,                                          /* tp_getattr */
230     0,                                          /* tp_setattr */
231     0,                                          /* tp_compare */
232     0,                                          /* tp_repr */
233     0,                                          /* tp_as_number */
234     0,                                          /* tp_as_sequence */
235     0,                                          /* tp_as_mapping */
236     0,                                          /* tp_hash */
237     0,                                          /* tp_call */
238     0,                                          /* tp_str */
239     0,                                          /* tp_getattro */
240     0,                                          /* tp_setattro */
241     0,                                          /* tp_as_buffer */
242     Py_TPFLAGS_DEFAULT,                         /* tp_flags */
243 };
244 
init_cffi_tls_zombie(void)245 static void init_cffi_tls_zombie(void)
246 {
247     cffi_zombie_head.zombie_next = &cffi_zombie_head;
248     cffi_zombie_head.zombie_prev = &cffi_zombie_head;
249     cffi_zombie_lock = PyThread_allocate_lock();
250     if (cffi_zombie_lock == NULL)
251         PyErr_SetString(PyExc_SystemError, "can't allocate cffi_zombie_lock");
252 }
253 
cffi_thread_shutdown(void * p)254 static void cffi_thread_shutdown(void *p)
255 {
256     /* this function is called from misc_thread_posix or misc_win32
257        when a thread is about to end. */
258     struct cffi_tls_s *tls = (struct cffi_tls_s *)p;
259 
260     /* thread-safety: this field 'local_thread_canary' can be reset
261        to NULL in parallel, protected by TLS_ZOM_LOCK. */
262     TLS_ZOM_LOCK();
263     if (tls->local_thread_canary != NULL) {
264         tls->local_thread_canary->tls = NULL;
265         thread_canary_make_zombie(tls->local_thread_canary);
266     }
267     TLS_ZOM_UNLOCK();
268     //fprintf(stderr, "thread_shutdown(%p)\n", tls);
269     free(tls);
270 }
271 
272 /* USE__THREAD is defined by setup.py if it finds that it is
273    syntactically valid to use "__thread" with this C compiler. */
274 #ifdef USE__THREAD
275 
276 static __thread int cffi_saved_errno = 0;
save_errno_only(void)277 static void save_errno_only(void) { cffi_saved_errno = errno; }
restore_errno_only(void)278 static void restore_errno_only(void) { errno = cffi_saved_errno; }
279 
280 #else
281 
save_errno_only(void)282 static void save_errno_only(void)
283 {
284     int saved = errno;
285     struct cffi_tls_s *tls = get_cffi_tls();
286     if (tls != NULL)
287         tls->saved_errno = saved;
288 }
289 
restore_errno_only(void)290 static void restore_errno_only(void)
291 {
292     struct cffi_tls_s *tls = get_cffi_tls();
293     if (tls != NULL)
294         errno = tls->saved_errno;
295 }
296 
297 #endif
298 
299 
300 /* MESS.  We can't use PyThreadState_GET(), because that calls
301    PyThreadState_Get() which fails an assert if the result is NULL.
302 
303    * in Python 2.7 and <= 3.4, the variable _PyThreadState_Current
304      is directly available, so use that.
305 
306    * in Python 3.5, the variable is available too, but it might be
307      the case that the headers don't define it (this changed in 3.5.1).
308      In case we're compiling with 3.5.x with x >= 1, we need to
309      manually define this variable.
310 
311    * in Python >= 3.6 there is _PyThreadState_UncheckedGet().
312      It was added in 3.5.2 but should never be used in 3.5.x
313      because it is not available in 3.5.0 or 3.5.1.
314 */
315 #if PY_VERSION_HEX >= 0x03050100 && PY_VERSION_HEX < 0x03060000
316 PyAPI_DATA(void *volatile) _PyThreadState_Current;
317 #endif
318 
get_current_ts(void)319 static PyThreadState *get_current_ts(void)
320 {
321 #if PY_VERSION_HEX >= 0x03060000
322     return _PyThreadState_UncheckedGet();
323 #elif defined(_Py_atomic_load_relaxed)
324     return (PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current);
325 #else
326     return (PyThreadState*)_PyThreadState_Current;  /* assume atomic read */
327 #endif
328 }
329 
gil_ensure(void)330 static PyGILState_STATE gil_ensure(void)
331 {
332     /* Called at the start of a callback.  Replacement for
333        PyGILState_Ensure().
334     */
335     PyGILState_STATE result;
336     PyThreadState *ts = PyGILState_GetThisThreadState();
337 
338     if (ts != NULL) {
339         ts->gilstate_counter++;
340         if (ts != get_current_ts()) {
341             /* common case: 'ts' is our non-current thread state and
342                we have to make it current and acquire the GIL */
343             PyEval_RestoreThread(ts);
344             return PyGILState_UNLOCKED;
345         }
346         else {
347             return PyGILState_LOCKED;
348         }
349     }
350     else {
351         /* no thread state here so far. */
352         result = PyGILState_Ensure();
353         assert(result == PyGILState_UNLOCKED);
354 
355         ts = PyGILState_GetThisThreadState();
356         assert(ts != NULL);
357         assert(ts == get_current_ts());
358         assert(ts->gilstate_counter >= 1);
359 
360         /* Use the ThreadCanary mechanism to keep 'ts' alive until the
361            thread really shuts down */
362         thread_canary_register(ts);
363 
364         return result;
365     }
366 }
367 
gil_release(PyGILState_STATE oldstate)368 static void gil_release(PyGILState_STATE oldstate)
369 {
370     PyGILState_Release(oldstate);
371 }
372