Author: Armin Rigo <ar...@tunes.org>
Branch: static-callback-embedding
Changeset: r2499:2075cb3f448f
Date: 2015-12-29 14:33 +0100
http://bitbucket.org/cffi/cffi/changeset/2075cb3f448f/

Log:    hg merge default

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -266,18 +266,7 @@
 /* whenever running Python code, the errno is saved in this thread-local
    variable */
 #ifndef MS_WIN32
-# ifdef USE__THREAD
-/* This macro ^^^ is defined by setup.py if it finds that it is
-   syntactically valid to use "__thread" with this C compiler. */
-static __thread int cffi_saved_errno = 0;
-static void save_errno(void) { cffi_saved_errno = errno; }
-static void restore_errno(void) { errno = cffi_saved_errno; }
-static void init_errno(void) { }
-# else
-#  include "misc_thread.h"
-# endif
-# define save_errno_only      save_errno
-# define restore_errno_only   restore_errno
+# include "misc_thread_posix.h"
 #endif
 
 #include "minibuffer.h"
@@ -290,8 +279,11 @@
 # include "wchar_helper.h"
 #endif
 
-typedef PyObject *const cffi_allocator_t[3];
-static cffi_allocator_t default_allocator = { NULL, NULL, NULL };
+typedef struct _cffi_allocator_s {
+    PyObject *ca_alloc, *ca_free;
+    int ca_dont_clear;
+} cffi_allocator_t;
+static const cffi_allocator_t default_allocator = { NULL, NULL, 0 };
 static PyObject *FFIError;
 static PyObject *unique_cache;
 
@@ -3030,21 +3022,18 @@
 static CDataObject *allocate_with_allocator(Py_ssize_t basesize,
                                             Py_ssize_t datasize,
                                             CTypeDescrObject *ct,
-                                            cffi_allocator_t allocator)
+                                            const cffi_allocator_t *allocator)
 {
     CDataObject *cd;
-    PyObject *my_alloc = allocator[0];
-    PyObject *my_free = allocator[1];
-    PyObject *dont_clear_after_alloc = allocator[2];
-
-    if (my_alloc == NULL) {   /* alloc */
+
+    if (allocator->ca_alloc == NULL) {
         cd = allocate_owning_object(basesize + datasize, ct);
         if (cd == NULL)
             return NULL;
         cd->c_data = ((char *)cd) + basesize;
     }
     else {
-        PyObject *res = PyObject_CallFunction(my_alloc, "n", datasize);
+        PyObject *res = PyObject_CallFunction(allocator->ca_alloc, "n", 
datasize);
         if (res == NULL)
             return NULL;
 
@@ -3069,16 +3058,16 @@
             return NULL;
         }
 
-        cd = allocate_gcp_object(cd, ct, my_free);
+        cd = allocate_gcp_object(cd, ct, allocator->ca_free);
         Py_DECREF(res);
     }
-    if (dont_clear_after_alloc == NULL)
+    if (!allocator->ca_dont_clear)
         memset(cd->c_data, 0, datasize);
     return cd;
 }
 
 static PyObject *direct_newp(CTypeDescrObject *ct, PyObject *init,
-                             cffi_allocator_t allocator)
+                             const cffi_allocator_t *allocator)
 {
     CTypeDescrObject *ctitem;
     CDataObject *cd;
@@ -3183,7 +3172,7 @@
     PyObject *init = Py_None;
     if (!PyArg_ParseTuple(args, "O!|O:newp", &CTypeDescr_Type, &ct, &init))
         return NULL;
-    return direct_newp(ct, init, default_allocator);
+    return direct_newp(ct, init, &default_allocator);
 }
 
 static int
@@ -4659,7 +4648,9 @@
 
     if (cif_descr != NULL) {
         /* exchange data size */
-        cif_descr->exchange_size = exchange_offset;
+        /* we also align it to the next multiple of 8, in an attempt to
+           work around bugs(?) of libffi like #241 */
+        cif_descr->exchange_size = ALIGN_ARG(exchange_offset);
     }
     return 0;
 }
@@ -5101,15 +5092,9 @@
 {
     save_errno();
     {
-#ifdef WITH_THREAD
-        PyGILState_STATE state = PyGILState_Ensure();
-#endif
-
+        PyGILState_STATE state = gil_ensure();
         general_invoke_callback(1, result, (char *)args, userdata);
-
-#ifdef WITH_THREAD
-        PyGILState_Release(state);
-#endif
+        gil_release(state);
     }
     restore_errno();
 }
@@ -6515,7 +6500,7 @@
     if (v == NULL || PyModule_AddObject(m, "_C_API", v) < 0)
         INITERROR;
 
-    v = PyText_FromString("1.3.1");
+    v = PyText_FromString("1.4.2");
     if (v == NULL || PyModule_AddObject(m, "__version__", v) < 0)
         INITERROR;
 
@@ -6542,7 +6527,7 @@
             INITERROR;
     }
 
-    init_errno();
+    init_cffi_tls();
     if (PyErr_Occurred())
         INITERROR;
 
diff --git a/c/call_python.c b/c/call_python.c
--- a/c/call_python.c
+++ b/c/call_python.c
@@ -24,9 +24,6 @@
 
 static PyObject *_ffi_def_extern_decorator(PyObject *outer_args, PyObject *fn)
 {
-#if PY_MAJOR_VERSION >= 3
-#  error review!
-#endif
     char *s;
     PyObject *error, *onerror, *infotuple, *old1;
     int index, err;
@@ -43,10 +40,10 @@
         return NULL;
 
     if (s == NULL) {
-        PyObject *name = PyObject_GetAttrString(fn, "__name__");
+        name = PyObject_GetAttrString(fn, "__name__");
         if (name == NULL)
             return NULL;
-        s = PyString_AsString(name);
+        s = PyText_AsUTF8(name);
         if (s == NULL) {
             Py_DECREF(name);
             return NULL;
@@ -203,9 +200,7 @@
         err = 1;
     }
     else {
-#ifdef WITH_THREAD
-        PyGILState_STATE state = PyGILState_Ensure();
-#endif
+        PyGILState_STATE state = gil_ensure();
         if (externpy->reserved1 != PyThreadState_GET()->interp->modules) {
             /* Update the (reserved1, reserved2) cache.  This will fail
                if we didn't call @ffi.def_extern() in this particular
@@ -215,9 +210,7 @@
         if (!err) {
             general_invoke_callback(0, args, args, externpy->reserved2);
         }
-#ifdef WITH_THREAD
-        PyGILState_Release(state);
-#endif
+        gil_release(state);
     }
     if (err) {
         static const char *msg[2] = {
diff --git a/c/cffi1_module.c b/c/cffi1_module.c
--- a/c/cffi1_module.c
+++ b/c/cffi1_module.c
@@ -22,7 +22,7 @@
 static int init_ffi_lib(PyObject *m)
 {
     PyObject *x;
-    int i;
+    int i, res;
     static char init_done = 0;
 
     if (PyType_Ready(&FFI_Type) < 0)
@@ -48,11 +48,13 @@
 
         for (i = 0; all_dlopen_flags[i].name != NULL; i++) {
             x = PyInt_FromLong(all_dlopen_flags[i].value);
-            if (x == NULL || PyDict_SetItemString(FFI_Type.tp_dict,
-                                                  all_dlopen_flags[i].name,
-                                                  x) < 0)
+            if (x == NULL)
                 return -1;
+            res = PyDict_SetItemString(FFI_Type.tp_dict,
+                                       all_dlopen_flags[i].name, x);
             Py_DECREF(x);
+            if (res < 0)
+                return -1;
         }
         init_done = 1;
     }
diff --git a/c/commontypes.c b/c/commontypes.c
--- a/c/commontypes.c
+++ b/c/commontypes.c
@@ -199,7 +199,8 @@
 
 static PyObject *b__get_common_types(PyObject *self, PyObject *arg)
 {
-    int i, err;
+    int err;
+    size_t i;
     for (i = 0; i < num_common_simple_types; i++) {
         const char *s = common_simple_types[i];
         PyObject *o = PyText_FromString(s + strlen(s) + 1);
diff --git a/c/ffi_obj.c b/c/ffi_obj.c
--- a/c/ffi_obj.c
+++ b/c/ffi_obj.c
@@ -335,7 +335,7 @@
 "pointer to the memory somewhere else, e.g. into another structure.");
 
 static PyObject *_ffi_new(FFIObject *self, PyObject *args, PyObject *kwds,
-                          cffi_allocator_t allocator)
+                          const cffi_allocator_t *allocator)
 {
     CTypeDescrObject *ct;
     PyObject *arg, *init = Py_None;
@@ -353,15 +353,22 @@
 
 static PyObject *ffi_new(FFIObject *self, PyObject *args, PyObject *kwds)
 {
-    return _ffi_new(self, args, kwds, default_allocator);
+    return _ffi_new(self, args, kwds, &default_allocator);
 }
 
 static PyObject *_ffi_new_with_allocator(PyObject *allocator, PyObject *args,
                                          PyObject *kwds)
 {
+    cffi_allocator_t alloc1;
+    PyObject *my_alloc, *my_free;
+    my_alloc = PyTuple_GET_ITEM(allocator, 1);
+    my_free  = PyTuple_GET_ITEM(allocator, 2);
+    alloc1.ca_alloc = (my_alloc == Py_None ? NULL : my_alloc);
+    alloc1.ca_free  = (my_free  == Py_None ? NULL : my_free);
+    alloc1.ca_dont_clear = (PyTuple_GET_ITEM(allocator, 3) == Py_False);
+
     return _ffi_new((FFIObject *)PyTuple_GET_ITEM(allocator, 0),
-                    args, kwds,
-                    &PyTuple_GET_ITEM(allocator, 1));
+                    args, kwds, &alloc1);
 }
 
 PyDoc_STRVAR(ffi_new_allocator_doc,
@@ -396,27 +403,14 @@
         return NULL;
     }
 
-    allocator = PyTuple_New(4);
+    allocator = PyTuple_Pack(4,
+                             (PyObject *)self,
+                             my_alloc,
+                             my_free,
+                             PyBool_FromLong(should_clear_after_alloc));
     if (allocator == NULL)
         return NULL;
 
-    Py_INCREF(self);
-    PyTuple_SET_ITEM(allocator, 0, (PyObject *)self);
-
-    if (my_alloc != Py_None) {
-        Py_INCREF(my_alloc);
-        PyTuple_SET_ITEM(allocator, 1, my_alloc);
-    }
-    if (my_free != Py_None) {
-        Py_INCREF(my_free);
-        PyTuple_SET_ITEM(allocator, 2, my_free);
-    }
-    if (!should_clear_after_alloc) {
-        PyObject *my_true = Py_True;
-        Py_INCREF(my_true);
-        PyTuple_SET_ITEM(allocator, 3, my_true);  /* dont_clear_after_alloc */
-    }
-
     {
         static PyMethodDef md = {"allocator",
                                  (PyCFunction)_ffi_new_with_allocator,
@@ -896,7 +890,14 @@
 #endif
 
 PyDoc_STRVAR(ffi_init_once_doc,
-             "XXX document me");
+"init_once(function, tag): run function() once.  More precisely,\n"
+"'function()' is called the first time we see a given 'tag'.\n"
+"\n"
+"The return value of function() is remembered and returned by the current\n"
+"and all future init_once() with the same tag.  If init_once() is called\n"
+"from multiple threads in parallel, all calls block until the execution\n"
+"of function() is done.  If function() raises an exception, it is\n"
+"propagated and nothing is cached.");
 
 #if PY_MAJOR_VERSION < 3
 /* PyCapsule_New is redefined to be PyCObject_FromVoidPtr in _cffi_backend,
diff --git a/c/lib_obj.c b/c/lib_obj.c
--- a/c/lib_obj.c
+++ b/c/lib_obj.c
@@ -459,6 +459,7 @@
 
 static PyObject *lib_getattr(LibObject *lib, PyObject *name)
 {
+    char *p;
     PyObject *x;
     LIB_GET_OR_CACHE_ADDR(x, lib, name, goto missing);
 
@@ -469,16 +470,25 @@
     return x;
 
  missing:
-    if (strcmp(PyText_AsUTF8(name), "__all__") == 0) {
+    p = PyText_AsUTF8(name);
+    if (p == NULL)
+        return NULL;
+    if (strcmp(p, "__all__") == 0) {
         PyErr_Clear();
         return _lib_dir1(lib, 1);
     }
-    if (strcmp(PyText_AsUTF8(name), "__dict__") == 0) {
+    if (strcmp(p, "__dict__") == 0) {
         PyErr_Clear();
         return _lib_dict(lib);
     }
+    if (strcmp(p, "__class__") == 0) {
+        PyErr_Clear();
+        x = (PyObject *)Py_TYPE(lib);
+        Py_INCREF(x);
+        return x;
+    }
     /* this hack is for Python 3.5 */
-    if (strcmp(PyText_AsUTF8(name), "__name__") == 0) {
+    if (strcmp(p, "__name__") == 0) {
         PyErr_Clear();
         return lib_repr(lib);
     }
diff --git a/c/misc_thread.h b/c/misc_thread.h
deleted file mode 100644
--- a/c/misc_thread.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#include <pthread.h>
-
-/* This is only included if GCC doesn't support "__thread" global variables.
- * See USE__THREAD in _ffi_backend.c.
- */
-
-static pthread_key_t cffi_tls_key;
-
-static void init_errno(void)
-{
-    (void) pthread_key_create(&cffi_tls_key, NULL);
-}
-
-static void save_errno(void)
-{
-    intptr_t value = errno;
-    (void) pthread_setspecific(cffi_tls_key, (void *)value);
-}
-
-static void restore_errno(void) {
-    intptr_t value = (intptr_t)pthread_getspecific(cffi_tls_key);
-    errno = value;
-}
diff --git a/c/misc_thread_posix.h b/c/misc_thread_posix.h
new file mode 100644
--- /dev/null
+++ b/c/misc_thread_posix.h
@@ -0,0 +1,186 @@
+/*
+  Logic for a better replacement of PyGILState_Ensure().
+
+  This version is ready to handle the case of a non-Python-started
+  thread in which we do a large number of calls to CFFI callbacks.  If
+  we were to rely on PyGILState_Ensure() for that, we would constantly
+  be creating and destroying PyThreadStates---it is slow, and
+  PyThreadState_Delete() will actually walk the list of all thread
+  states, making it O(n). :-(
+
+  This version only creates one PyThreadState object the first time we
+  see a given thread, and keep it alive until the thread is really
+  shut down, using a destructor on the tls key.
+*/
+
+#ifdef WITH_THREAD
+#include <pthread.h>
+
+
+static pthread_key_t cffi_tls_key;
+
+struct cffi_tls_s {
+    /* The locally-made thread state.  This is only non-null in case
+       we build the thread state here.  It remains null if this thread
+       had already a thread state provided by CPython. */
+    PyThreadState *local_thread_state;
+
+    /* The saved errno.  If the C compiler supports '__thread', then
+       we use that instead; this value is not used at all in this case. */
+    int saved_errno;
+};
+
+static void _tls_destructor(void *p)
+{
+    struct cffi_tls_s *tls = (struct cffi_tls_s *)p;
+
+    if (tls->local_thread_state != NULL) {
+        /* We need to re-acquire the GIL temporarily to free the
+           thread state.  I hope it is not a problem to do it in
+           a thread-local destructor.
+        */
+        PyEval_RestoreThread(tls->local_thread_state);
+        PyThreadState_DeleteCurrent();
+    }
+    free(tls);
+}
+
+static void init_cffi_tls(void)
+{
+    if (pthread_key_create(&cffi_tls_key, _tls_destructor) != 0)
+        PyErr_SetString(PyExc_OSError, "pthread_key_create() failed");
+}
+
+static struct cffi_tls_s *_make_cffi_tls(void)
+{
+    void *p = calloc(1, sizeof(struct cffi_tls_s));
+    if (p == NULL)
+        return NULL;
+    if (pthread_setspecific(cffi_tls_key, p) != 0) {
+        free(p);
+        return NULL;
+    }
+    return p;
+}
+
+static struct cffi_tls_s *get_cffi_tls(void)
+{
+    void *p = pthread_getspecific(cffi_tls_key);
+    if (p == NULL)
+        p = _make_cffi_tls();
+    return (struct cffi_tls_s *)p;
+}
+
+
+/* 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(void) { cffi_saved_errno = errno; }
+static void restore_errno(void) { errno = cffi_saved_errno; }
+
+#else
+
+static void save_errno(void)
+{
+    int saved = errno;
+    struct cffi_tls_s *tls = get_cffi_tls();
+    if (tls != NULL)
+        tls->saved_errno = saved;
+}
+
+static void restore_errno(void)
+{
+    struct cffi_tls_s *tls = get_cffi_tls();
+    if (tls != NULL)
+        errno = tls->saved_errno;
+}
+
+#endif
+
+
+/* Seems that CPython 3.5.1 made our job harder.  Did not find out how
+   to do that without these hacks.  We can't use PyThreadState_GET(),
+   because that calls PyThreadState_Get() which fails an assert if the
+   result is NULL. */
+#if PY_MAJOR_VERSION >= 3 && !defined(_Py_atomic_load_relaxed)
+                             /* this was abruptly un-defined in 3.5.1 */
+void *volatile _PyThreadState_Current;
+   /* XXX simple volatile access is assumed atomic */
+#  define _Py_atomic_load_relaxed(pp)  (*(pp))
+#endif
+
+
+static PyThreadState *get_current_ts(void)
+{
+#if PY_MAJOR_VERSION >= 3
+    return (PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current);
+#else
+    return _PyThreadState_Current;
+#endif
+}
+
+static PyGILState_STATE gil_ensure(void)
+{
+    /* Called at the start of a callback.  Replacement for
+       PyGILState_Ensure().
+    */
+    PyGILState_STATE result;
+    struct cffi_tls_s *tls;
+    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);
+
+        /* Save the now-current thread state inside our 'local_thread_state'
+           field, to be removed at thread shutdown */
+        tls = get_cffi_tls();
+        if (tls != NULL) {
+            tls->local_thread_state = ts;
+            ts->gilstate_counter++;
+        }
+
+        return result;
+    }
+}
+
+static void gil_release(PyGILState_STATE oldstate)
+{
+    PyGILState_Release(oldstate);
+}
+
+
+#else   /* !WITH_THREAD */
+
+static int cffi_saved_errno = 0;
+static void save_errno(void) { cffi_saved_errno = errno; }
+static void restore_errno(void) { errno = cffi_saved_errno; }
+
+static PyGILState_STATE gil_ensure(void) { return -1; }
+static void gil_release(PyGILState_STATE oldstate) { }
+
+#endif  /* !WITH_THREAD */
+
+
+#define save_errno_only      save_errno
+#define restore_errno_only   restore_errno
diff --git a/c/misc_win32.h b/c/misc_win32.h
--- a/c/misc_win32.h
+++ b/c/misc_win32.h
@@ -10,7 +10,7 @@
 
 static DWORD cffi_tls_index = TLS_OUT_OF_INDEXES;
 
-static void init_errno(void)
+static void init_cffi_tls(void)
 {
     if (cffi_tls_index == TLS_OUT_OF_INDEXES) {
         cffi_tls_index = TlsAlloc();
@@ -182,6 +182,17 @@
 }
 #endif
 
+
+#ifdef WITH_THREAD
+/* XXX should port the code from misc_thread_posix.h */
+static PyGILState_STATE gil_ensure(void) { return PyGILState_Ensure(); }
+static void gil_release(PyGILState_STATE oldst) { PyGILState_Release(oldst); }
+#else
+static PyGILState_STATE gil_ensure(void) { return -1; }
+static void gil_release(PyGILState_STATE oldstate) { }
+#endif
+
+
 /************************************************************/
 /* Emulate dlopen()&co. from the Windows API */
 
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -12,7 +12,7 @@
 # ____________________________________________________________
 
 import sys
-assert __version__ == "1.3.1", ("This test_c.py file is for testing a version"
+assert __version__ == "1.4.2", ("This test_c.py file is for testing a version"
                                 " of cffi that differs from the one that we"
                                 " get from 'import _cffi_backend'")
 if sys.version_info < (3,):
diff --git a/cffi/__init__.py b/cffi/__init__.py
--- a/cffi/__init__.py
+++ b/cffi/__init__.py
@@ -4,8 +4,8 @@
 from .api import FFI, CDefError, FFIError
 from .ffiplatform import VerificationError, VerificationMissing
 
-__version__ = "1.3.1"
-__version_info__ = (1, 3, 1)
+__version__ = "1.4.2"
+__version_info__ = (1, 4, 2)
 
 # The verifier module file names are based on the CRC32 of a string that
 # contains the following version number.  It may be older than __version__
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -73,6 +73,7 @@
         self._included_ffis = []
         self._windows_unicode = None
         self._init_once_cache = {}
+        self._cdef_version = None
         self._embedding_init_code = None
         if hasattr(backend, 'set_ffi'):
             backend.set_ffi(self)
@@ -106,6 +107,7 @@
                 raise TypeError("cdef() argument must be a string")
             csource = csource.encode('ascii')
         with self._lock:
+            self._cdef_version = object()
             self._parser.parse(csource, override=override, packed=packed,
                                dllexport=dllexport)
             self._cdefsources.append(csource)
@@ -592,14 +594,15 @@
         recompile(self, module_name, source,
                   c_file=filename, call_c_compiler=False, **kwds)
 
-    def compile(self, tmpdir='.'):
+    def compile(self, tmpdir='.', verbose=0):
         from .recompiler import recompile
         #
         if not hasattr(self, '_assigned_source'):
             raise ValueError("set_source() must be called before compile()")
         module_name, source, source_extension, kwds = self._assigned_source
         return recompile(self, module_name, source, tmpdir=tmpdir,
-                         source_extension=source_extension, **kwds)
+                         source_extension=source_extension,
+                         compiler_verbose=verbose, **kwds)
 
     def init_once(self, func, tag):
         # Read _init_once_cache[tag], which is either (False, lock) if
@@ -660,70 +663,70 @@
     import os
     backend = ffi._backend
     backendlib = _load_backend_lib(backend, libname, flags)
-    copied_enums = []
     #
-    def make_accessor_locked(name):
+    def accessor_function(name):
         key = 'function ' + name
-        if key in ffi._parser._declarations:
-            tp, _ = ffi._parser._declarations[key]
-            BType = ffi._get_cached_btype(tp)
-            try:
-                value = backendlib.load_function(BType, name)
-            except KeyError as e:
-                raise AttributeError('%s: %s' % (name, e))
-            library.__dict__[name] = value
+        tp, _ = ffi._parser._declarations[key]
+        BType = ffi._get_cached_btype(tp)
+        try:
+            value = backendlib.load_function(BType, name)
+        except KeyError as e:
+            raise AttributeError('%s: %s' % (name, e))
+        library.__dict__[name] = value
+    #
+    def accessor_variable(name):
+        key = 'variable ' + name
+        tp, _ = ffi._parser._declarations[key]
+        BType = ffi._get_cached_btype(tp)
+        read_variable = backendlib.read_variable
+        write_variable = backendlib.write_variable
+        setattr(FFILibrary, name, property(
+            lambda self: read_variable(BType, name),
+            lambda self, value: write_variable(BType, name, value)))
+    #
+    def accessor_constant(name):
+        raise NotImplementedError("non-integer constant '%s' cannot be "
+                                  "accessed from a dlopen() library" % (name,))
+    #
+    def accessor_int_constant(name):
+        library.__dict__[name] = ffi._parser._int_constants[name]
+    #
+    accessors = {}
+    accessors_version = [False]
+    #
+    def update_accessors():
+        if accessors_version[0] is ffi._cdef_version:
             return
         #
-        key = 'variable ' + name
-        if key in ffi._parser._declarations:
-            tp, _ = ffi._parser._declarations[key]
-            BType = ffi._get_cached_btype(tp)
-            read_variable = backendlib.read_variable
-            write_variable = backendlib.write_variable
-            setattr(FFILibrary, name, property(
-                lambda self: read_variable(BType, name),
-                lambda self, value: write_variable(BType, name, value)))
-            return
-        #
-        if not copied_enums:
-            from . import model
-            error = None
-            for key, (tp, _) in ffi._parser._declarations.items():
-                if not isinstance(tp, model.EnumType):
-                    continue
-                try:
-                    tp.check_not_partial()
-                except Exception as e:
-                    error = e
-                    continue
-                for enumname, enumval in zip(tp.enumerators, tp.enumvalues):
-                    if enumname not in library.__dict__:
-                        library.__dict__[enumname] = enumval
-            if error is not None:
-                if name in library.__dict__:
-                    return     # ignore error, about a different enum
-                raise error
-
-            for key, val in ffi._parser._int_constants.items():
-                if key not in library.__dict__:
-                    library.__dict__[key] = val
-
-            copied_enums.append(True)
-            if name in library.__dict__:
-                return
-        #
-        key = 'constant ' + name
-        if key in ffi._parser._declarations:
-            raise NotImplementedError("fetching a non-integer constant "
-                                      "after dlopen()")
-        #
-        raise AttributeError(name)
+        from . import model
+        for key, (tp, _) in ffi._parser._declarations.items():
+            if not isinstance(tp, model.EnumType):
+                tag, name = key.split(' ', 1)
+                if tag == 'function':
+                    accessors[name] = accessor_function
+                elif tag == 'variable':
+                    accessors[name] = accessor_variable
+                elif tag == 'constant':
+                    accessors[name] = accessor_constant
+            else:
+                for i, enumname in enumerate(tp.enumerators):
+                    def accessor_enum(name, tp=tp, i=i):
+                        tp.check_not_partial()
+                        library.__dict__[name] = tp.enumvalues[i]
+                    accessors[enumname] = accessor_enum
+        for name in ffi._parser._int_constants:
+            accessors.setdefault(name, accessor_int_constant)
+        accessors_version[0] = ffi._cdef_version
     #
     def make_accessor(name):
         with ffi._lock:
             if name in library.__dict__ or name in FFILibrary.__dict__:
                 return    # added by another thread while waiting for the lock
-            make_accessor_locked(name)
+            if name not in accessors:
+                update_accessors()
+                if name not in accessors:
+                    raise AttributeError(name)
+            accessors[name](name)
     #
     class FFILibrary(object):
         def __getattr__(self, name):
@@ -737,6 +740,10 @@
                 setattr(self, name, value)
             else:
                 property.__set__(self, value)
+        def __dir__(self):
+            with ffi._lock:
+                update_accessors()
+                return accessors.keys()
     #
     if libname is not None:
         try:
diff --git a/cffi/ffiplatform.py b/cffi/ffiplatform.py
--- a/cffi/ffiplatform.py
+++ b/cffi/ffiplatform.py
@@ -17,15 +17,16 @@
 def get_extension(srcfilename, modname, sources=(), **kwds):
     from distutils.core import Extension
     allsources = [srcfilename]
-    allsources.extend(sources)
+    for src in sources:
+        allsources.append(os.path.normpath(src))
     return Extension(name=modname, sources=allsources, **kwds)
 
-def compile(tmpdir, ext):
+def compile(tmpdir, ext, compiler_verbose=0):
     """Compile a C extension module using distutils."""
 
     saved_environ = os.environ.copy()
     try:
-        outputfilename = _build(tmpdir, ext)
+        outputfilename = _build(tmpdir, ext, compiler_verbose)
         outputfilename = os.path.abspath(outputfilename)
     finally:
         # workaround for a distutils bugs where some env vars can
@@ -35,10 +36,10 @@
                 os.environ[key] = value
     return outputfilename
 
-def _build(tmpdir, ext):
+def _build(tmpdir, ext, compiler_verbose=0):
     # XXX compact but horrible :-(
     from distutils.core import Distribution
-    import distutils.errors
+    import distutils.errors, distutils.log
     #
     dist = Distribution({'ext_modules': [ext]})
     dist.parse_config_files()
@@ -48,7 +49,12 @@
     options['build_temp'] = ('ffiplatform', tmpdir)
     #
     try:
-        dist.run_command('build_ext')
+        old_level = distutils.log.set_threshold(0) or 0
+        try:
+            distutils.log.set_verbosity(compiler_verbose)
+            dist.run_command('build_ext')
+        finally:
+            distutils.log.set_threshold(old_level)
     except (distutils.errors.CompileError,
             distutils.errors.LinkError) as e:
         raise VerificationError('%s: %s' % (e.__class__.__name__, e))
diff --git a/cffi/recompiler.py b/cffi/recompiler.py
--- a/cffi/recompiler.py
+++ b/cffi/recompiler.py
@@ -1354,7 +1354,8 @@
     return os.path.join(outputdir, *parts), parts
 
 def recompile(ffi, module_name, preamble, tmpdir='.', call_c_compiler=True,
-              c_file=None, source_extension='.c', extradir=None, **kwds):
+              c_file=None, source_extension='.c', extradir=None,
+              compiler_verbose=1, **kwds):
     if not isinstance(module_name, str):
         module_name = module_name.encode('ascii')
     if ffi._windows_unicode:
@@ -1374,7 +1375,7 @@
             cwd = os.getcwd()
             try:
                 os.chdir(tmpdir)
-                outputfilename = ffiplatform.compile('.', ext)
+                outputfilename = ffiplatform.compile('.', ext, 
compiler_verbose)
             finally:
                 os.chdir(cwd)
             return outputfilename
diff --git a/doc/source/cdef.rst b/doc/source/cdef.rst
--- a/doc/source/cdef.rst
+++ b/doc/source/cdef.rst
@@ -62,7 +62,7 @@
     ffi.cdef("C-like declarations with '...'")
 
     if __name__ == "__main__":
-        ffi.compile()
+        ffi.compile(verbose=True)
 
   Running ``python foo_build.py`` produces a file ``_foo.c`` and
   invokes the C compiler to turn it into a file ``_foo.so`` (or
@@ -134,7 +134,8 @@
 Similarly, the ``lib`` objects returned by the C version are read-only,
 apart from writes to global variables.  Also, ``lib.__dict__`` does
 not work before version 1.2 or if ``lib`` happens to declare a name
-called ``__dict__`` (use instead ``dir(lib)``).
+called ``__dict__`` (use instead ``dir(lib)``).  The same is true
+for ``lib.__class__`` before version 1.4.
 
 
 ffi.cdef(): declaring types and functions
@@ -504,7 +505,8 @@
 the mtime to be updated anyway, delete the file before calling the
 functions.
 
-**ffi.compile(tmpdir='.'):** explicitly generate the .py or .c file,
+**ffi.compile(tmpdir='.', verbose=False):**
+explicitly generate the .py or .c file,
 and (if .c) compile it.  The output file is (or are) put in the
 directory given by ``tmpdir``.  In the examples given here, we use
 ``if __name__ == "__main__": ffi.compile()`` in the build scripts---if
@@ -513,6 +515,11 @@
 to ``set_source()``, then a corresponding subdirectory of the ``tmpdir``
 is used.)
 
+*New in version 1.4:* ``verbose`` argument.  If True, it prints the
+usual distutils output, including the command lines that call the
+compiler.  (This parameter might be changed to True by default in a
+future release.)
+
 **ffi.emit_python_code(filename):** generate the given .py file (same
 as ``ffi.compile()`` for ABI mode, with an explicitly-named file to
 write).  If you choose, you can include this .py file pre-packaged in
diff --git a/doc/source/conf.py b/doc/source/conf.py
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -45,9 +45,9 @@
 # built documents.
 #
 # The short X.Y version.
-version = '1.3'
+version = '1.4'
 # The full version, including alpha/beta/rc tags.
-release = '1.3.1'
+release = '1.4.2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -51,11 +51,11 @@
 
 Download and Installation:
 
-* http://pypi.python.org/packages/source/c/cffi/cffi-1.3.1.tar.gz
+* http://pypi.python.org/packages/source/c/cffi/cffi-1.4.2.tar.gz
 
-   - MD5: ...
+   - MD5: 81357fe5042d00650b85b728cc181df2
 
-   - SHA: ...
+   - SHA: 76cff6f1ff5bfb2b9c6c8e2cfa8bf90b5c944394
 
 * Or grab the most current version from the `Bitbucket page`_:
   ``hg clone https://bitbucket.org/cffi/cffi``
diff --git a/doc/source/using.rst b/doc/source/using.rst
--- a/doc/source/using.rst
+++ b/doc/source/using.rst
@@ -433,11 +433,11 @@
 out-of-line API mode.  The next section about Callbacks_ describes the
 ABI-mode solution.
 
-This is *new in version 1.4.*  Use Callbacks_ if backward compatibility
-is an issue.  (The original callbacks are slower to invoke and have
-the same issue as libffi's callbacks; notably, see the warning__.
-The  new style described in the present section does not use libffi's
-callbacks at all.)
+This is *new in version 1.4.*  Use old-style Callbacks_ if backward
+compatibility is an issue.  (The original callbacks are slower to
+invoke and have the same issue as libffi's callbacks; notably, see the
+warning__.  The new style described in the present section does not
+use libffi's callbacks at all.)
 
 .. __: Callbacks_
 
@@ -459,33 +459,35 @@
     from _my_example import ffi, lib
 
     @ffi.def_extern()
-    def my_callback(fooptr, value):
+    def my_callback(x, y):
         return 42
 
-You can get a ``<cdata>`` pointer-to-function object from
+You obtain a ``<cdata>`` pointer-to-function object by getting
 ``lib.my_callback``.  This ``<cdata>`` can be passed to C code and
 then works like a callback: when the C code calls this function
 pointer, the Python function ``my_callback`` is called.  (You need
 to pass ``lib.my_callback`` to C code, and not ``my_callback``: the
-latter is just a plain Python function that cannot be passed to C.)
+latter is just the Python function above, which cannot be passed to C.)
 
 CFFI implements this by defining ``my_callback`` as a static C
 function, written after the ``set_source()`` code.  The ``<cdata>``
 then points to this function.  What this function does is invoke the
-Python function object that was dynamically attached by
+Python function object that is, at runtime, attached with
 ``@ffi.def_extern()``.
 
-Each function from the cdef with ``extern "Python"`` turns into only
-one C function.  To support some corner cases, it is possible to
-redefine the attached Python function by calling ``@ffi.def_extern()``
-again---but this is not recommended!  Better write the Python function
-more flexibly in the first place.  Calling ``@ffi.def_extern()`` again
-changes the C logic to call the new Python function; the old Python
-function is not callable any more and the C function pointer you get
-from ``lib.my_function`` is always the same.
+The ``@ffi.def_extern()`` decorator should be applied to a global
+function, once.  This is because each function from the cdef with
+``extern "Python"`` turns into only one C function.  To support some
+corner cases, it is possible to redefine the attached Python function
+by calling ``@ffi.def_extern()`` again---but this is not recommended!
+Better write the single global Python function more flexibly in the
+first place.  Calling ``@ffi.def_extern()`` again changes the C logic
+to call the new Python function; the old Python function is not
+callable any more and the C function pointer you get from
+``lib.my_function`` is always the same.
 
-Extern "Python" and "void *" arguments
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Extern "Python" and ``void *`` arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 As described just before, you cannot use ``extern "Python"`` to make a
 variable number of C function pointers.  However, achieving that
@@ -567,11 +569,13 @@
 In case you want to access some ``extern "Python"`` function directly
 from the C code written in ``set_source()``, you need to write a
 forward static declaration.  The real implementation of this function
-is added by CFFI *after* the C code---this is needed because even the
+is added by CFFI *after* the C code---this is needed because the
 declaration might use types defined by ``set_source()``
 (e.g. ``event_t`` above, from the ``#include``), so it cannot be
 generated before.
 
+::
+
     ffi.set_source("_demo_cffi", """
         #include <the_event_library.h>
 
@@ -589,11 +593,12 @@
         int my_algo(int);
     """)
     ffi.set_source("_example_cffi", """
-        static int f(int);
+        static int f(int);   /* the forward declaration */
+
         static int my_algo(int n) {
             int i, sum = 0;
             for (i = 0; i < n; i++)
-                sum += f(i);
+                sum += f(i);     /* call f() here */
             return sum;
         }
     """)
@@ -705,7 +710,28 @@
 keep this object alive for as long as the callback may be invoked.
 The easiest way to do that is to always use ``@ffi.callback()`` at
 module-level only, and to pass "context" information around with
-`ffi.new_handle()`_, if possible.
+`ffi.new_handle()`_, if possible.  Example:
+
+.. code-block:: python
+
+    # a good way to use this decorator is once at global level
+    @ffi.callback("int(int, void *)")
+    def my_global_callback(x, handle):
+        return ffi.from_handle(handle).some_method(x)
+
+
+    class Foo(object):
+
+        def __init__(self):
+            handle = ffi.new_handle(self)
+            self._handle = handle   # must be kept alive
+            lib.register_stuff_with_callback_and_voidp_arg(my_global_callback, 
handle)
+
+        def some_method(self, x):
+            ...
+
+(See also the section about `extern "Python"`_ above, where the same
+general style is used.)
 
 Note that callbacks of a variadic function type are not supported.  A
 workaround is to add custom C code.  In the following example, a
@@ -797,7 +823,7 @@
 hurt) to say ``WINAPI`` or ``__stdcall`` when declaring a plain
 function in the ``cdef()``.  (The difference can still be seen if you
 take explicitly a pointer to this function with ``ffi.addressof()``,
-or if the function is ``CFFI_CALL_PYTHON``.)
+or if the function is ``extern "Python"``.)
 
 These calling convention specifiers are accepted but ignored on any
 platform other than 32-bit Windows.
@@ -1139,6 +1165,58 @@
 returned by ``alloc()`` is assumed to be already cleared (or you are
 fine with garbage); otherwise CFFI will clear it.
 
+.. _initonce:
+
+**ffi.init_once(function, tag)**: run ``function()`` once.  The
+``tag`` should be a primitive object, like a string, that identifies
+the function: ``function()`` is only called the first time we see the
+``tag``.  The return value of ``function()`` is remembered and
+returned by the current and all future ``init_once()`` with the same
+tag.  If ``init_once()`` is called from multiple threads in parallel,
+all calls block until the execution of ``function()`` is done.  If
+``function()`` raises an exception, it is propagated and nothing is
+cached (i.e. ``function()`` will be called again, in case we catch the
+exception and try ``init_once()`` again).  *New in version 1.4.*
+
+Example::
+
+    from _xyz_cffi import ffi, lib
+
+    def initlib():
+        lib.init_my_library()
+
+    def make_new_foo():
+        ffi.init_once(initlib, "init")
+        return lib.make_foo()
+
+``init_once()`` is optimized to run very quickly if ``function()`` has
+already been called.  (On PyPy, the cost is zero---the JIT usually
+removes everything in the machine code it produces.)
+
+*Note:* one motivation__ for ``init_once()`` is the CPython notion of
+"subinterpreters" in the embedded case.  If you are using the
+out-of-line API mode, ``function()`` is called only once even in the
+presence of multiple subinterpreters, and its return value is shared
+among all subinterpreters.  The goal is to mimic the way traditional
+CPython C extension modules have their init code executed only once in
+total even if there are subinterpreters.  In the example above, the C
+function ``init_my_library()`` is called once in total, not once per
+subinterpreter.  For this reason, avoid Python-level side-effects in
+``function()`` (as they will only be applied in the first
+subinterpreter to run); instead, return a value, as in the following
+example::
+
+   def init_get_max():
+       return lib.initialize_once_and_get_some_maximum_number()
+
+   def process(i):
+       if i > ffi.init_once(init_get_max, "max"):
+           raise IndexError("index too large!")
+       ...
+
+.. __: https://bitbucket.org/cffi/cffi/issues/233/
+
+
 .. _`Preparing and Distributing modules`: cdef.html#loading-libraries
 
 
@@ -1233,8 +1311,8 @@
    function with a ``char *`` argument to which you pass a Python
    string will not actually modify the array of characters passed in,
    and so passes directly a pointer inside the Python string object.
-   (PyPy might in the future do the same, but it is harder because a
-   string object can move in memory when the GC runs.)
+   (PyPy might in the future do the same, but it is harder because
+   strings are not naturally zero-terminated in PyPy.)
 
 `(**)` C function calls are done with the GIL released.
 
diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst
--- a/doc/source/whatsnew.rst
+++ b/doc/source/whatsnew.rst
@@ -3,18 +3,61 @@
 ======================
 
 
+v1.4.2
+======
+
+Nothing changed from v1.4.1.
+
+
+v1.4.1
+======
+
+* Fix the compilation failure of cffi on CPython 3.5.0.  (3.5.1 works;
+  some detail changed that makes some underscore-starting macros
+  disappear from view of extension modules, and I worked around it,
+  thinking it changed in all 3.5 versions---but no: it was only in
+  3.5.1.)
+
+
 v1.4.0
 ======
 
+* A `better way to do callbacks`__ has been added (faster and more
+  portable, and usually cleaner).  It is a mechanism for the
+  out-of-line API mode that replaces the dynamic creation of callback
+  objects (i.e. C functions that invoke Python) with the static
+  declaration in ``cdef()`` of which callbacks are needed.  This is
+  more C-like, in that you have to structure your code around the idea
+  that you get a fixed number of function pointers, instead of
+  creating them on-the-fly.
+
+* ``ffi.compile()`` now takes an optional ``verbose`` argument.  When
+  ``True``, distutils prints the calls to the compiler.
+
+* ``ffi.compile()`` used to fail if given ``sources`` with a path that
+  includes ``".."``.  Fixed.
+
+* ``ffi.init_once()`` added.  See docs__.
+
+* ``dir(lib)`` now works on libs returned by ``ffi.dlopen()`` too.
+
+* Cleaned up and modernized the content of the ``demo`` subdirectory
+  in the sources (thanks matti!).
+
 * ``ffi.new_handle()`` is now guaranteed to return unique ``void *``
   values, even if called twice on the same object.  Previously, in
-  that case, CPython (but not PyPy) would return two ``cdata`` objects
-  with the same ``void *`` value.  This change is useful to add and
-  remove handles from a global dict or set without worrying about
-  duplicates.
+  that case, CPython would return two ``cdata`` objects with the same
+  ``void *`` value.  This change is useful to add and remove handles
+  from a global dict (or set) without worrying about duplicates.
+  It already used to work like that on PyPy.
+  *This change can break code that used to work on CPython by relying
+  on the object to be kept alive by other means than keeping the
+  result of ffi.new_handle() alive.*  (The corresponding `warning in
+  the docs`__ of ``ffi.new_handle()`` has been here since v0.8!)
 
-* ``ffi.init_once()`` XXX
-  https://bitbucket.org/cffi/cffi/issues/233/
+.. __: using.html#extern-python
+.. __: using.html#initonce
+.. __: using.html#ffi-new-handle
 
 
 v1.3.1
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -144,7 +144,7 @@
 
 `Mailing list <https://groups.google.com/forum/#!forum/python-cffi>`_
 """,
-        version='1.3.1',
+        version='1.4.2',
         packages=['cffi'] if cpython else [],
         package_data={'cffi': ['_cffi_include.h', 'parse_c_type.h']}
                      if cpython else {},
diff --git a/testing/cffi0/backend_tests.py b/testing/cffi0/backend_tests.py
--- a/testing/cffi0/backend_tests.py
+++ b/testing/cffi0/backend_tests.py
@@ -1352,8 +1352,8 @@
         ffi = FFI(backend=self.Backend())
         ffi.cdef("enum foo;")
         from cffi import __version_info__
-        if __version_info__ < (1, 4):
-            py.test.skip("re-enable me in version 1.4")
+        if __version_info__ < (1, 5):
+            py.test.skip("re-enable me in version 1.5")
         e = py.test.raises(CDefError, ffi.cast, "enum foo", -1)
         assert str(e.value) == (
             "'enum foo' has no values explicitly defined: refusing to guess "
@@ -1826,7 +1826,12 @@
             assert seen == [1, 1]
 
     def test_init_once_multithread(self):
-        import thread, time
+        import sys, time
+        if sys.version_info < (3,):
+            import thread
+        else:
+            import _thread as thread
+        #
         def do_init():
             seen.append('init!')
             time.sleep(1)
diff --git a/testing/cffi0/test_function.py b/testing/cffi0/test_function.py
--- a/testing/cffi0/test_function.py
+++ b/testing/cffi0/test_function.py
@@ -464,10 +464,22 @@
         ffi = FFI(backend=self.Backend())
         ffi.cdef("double __stdcall sin(double x);")     # stdcall ignored
         m = ffi.dlopen(lib_m)
-        if (sys.platform == 'win32' and sys.maxsize < 2**32 and 
+        if (sys.platform == 'win32' and sys.maxsize < 2**32 and
                 self.Backend is not CTypesBackend):
             assert "double(__stdcall *)(double)" in str(ffi.typeof(m.sin))
         else:
             assert "double(*)(double)" in str(ffi.typeof(m.sin))
         x = m.sin(1.23)
         assert x == math.sin(1.23)
+
+    def test_dir_on_dlopen_lib(self):
+        ffi = FFI(backend=self.Backend())
+        ffi.cdef("""
+            typedef enum { MYE1, MYE2 } myenum_t;
+            double myfunc(double);
+            double myvar;
+            const double myconst;
+            #define MYFOO 42
+        """)
+        m = ffi.dlopen(lib_m)
+        assert dir(m) == ['MYE1', 'MYE2', 'MYFOO', 'myconst', 'myfunc', 
'myvar']
diff --git a/testing/cffi1/test_ffi_obj.py b/testing/cffi1/test_ffi_obj.py
--- a/testing/cffi1/test_ffi_obj.py
+++ b/testing/cffi1/test_ffi_obj.py
@@ -7,6 +7,7 @@
     p = ffi.new("int *")
     p[0] = -42
     assert p[0] == -42
+    assert type(ffi) is ffi.__class__ is _cffi1_backend.FFI
 
 def test_ffi_subclass():
     class FOO(_cffi1_backend.FFI):
@@ -16,6 +17,7 @@
     assert foo.x == 42
     p = foo.new("int *")
     assert p[0] == 0
+    assert type(foo) is foo.__class__ is FOO
 
 def test_ffi_no_argument():
     py.test.raises(TypeError, _cffi1_backend.FFI, 42)
@@ -437,13 +439,18 @@
         assert seen == [1, 1]
 
 def test_init_once_multithread():
-    import thread, time
+    if sys.version_info < (3,):
+        import thread
+    else:
+        import _thread as thread
+    import time
+    #
     def do_init():
-        print 'init!'
+        print('init!')
         seen.append('init!')
         time.sleep(1)
         seen.append('init done')
-        print 'init done'
+        print('init done')
         return 7
     ffi = _cffi1_backend.FFI()
     seen = []
@@ -454,3 +461,37 @@
         thread.start_new_thread(f, ())
     time.sleep(1.5)
     assert seen == ['init!', 'init done'] + 6 * [7]
+
+def test_init_once_failure():
+    def do_init():
+        seen.append(1)
+        raise ValueError
+    ffi = _cffi1_backend.FFI()
+    seen = []
+    for i in range(5):
+        py.test.raises(ValueError, ffi.init_once, do_init, "tag")
+        assert seen == [1] * (i + 1)
+
+def test_init_once_multithread_failure():
+    if sys.version_info < (3,):
+        import thread
+    else:
+        import _thread as thread
+    import time
+    def do_init():
+        seen.append('init!')
+        time.sleep(1)
+        seen.append('oops')
+        raise ValueError
+    ffi = _cffi1_backend.FFI()
+    seen = []
+    for i in range(3):
+        def f():
+            py.test.raises(ValueError, ffi.init_once, do_init, "tag")
+        thread.start_new_thread(f, ())
+    i = 0
+    while len(seen) < 6:
+        i += 1
+        assert i < 20
+        time.sleep(0.51)
+    assert seen == ['init!', 'oops'] * 3
diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py
--- a/testing/cffi1/test_recompiler.py
+++ b/testing/cffi1/test_recompiler.py
@@ -3,7 +3,7 @@
 from cffi import FFI, VerificationError, FFIError
 from cffi import recompiler
 from testing.udir import udir
-from testing.support import u
+from testing.support import u, long
 from testing.support import FdWriteCapture, StdErrCapture
 
 
@@ -948,6 +948,19 @@
     """, sources=[str(extra_c_source)])
     assert lib.external_foo == 42
 
+def test_dotdot_in_source_file_names():
+    extra_c_source = udir.join(
+        'extra_test_dotdot_in_source_file_names.c')
+    extra_c_source.write('const int external_foo = 42;\n')
+    ffi = FFI()
+    ffi.cdef("const int external_foo;")
+    lib = verify(ffi, 'test_dotdot_in_source_file_names', """
+        extern const int external_foo;
+    """, sources=[os.path.join(os.path.dirname(str(extra_c_source)),
+                               'foobar', '..',
+                               os.path.basename(str(extra_c_source)))])
+    assert lib.external_foo == 42
+
 def test_call_with_incomplete_structs():
     ffi = FFI()
     ffi.cdef("typedef struct {...;} foo_t; "
@@ -1143,6 +1156,7 @@
     assert hasattr(lib, '__dict__')
     assert lib.__all__ == ['MYFOO', 'mybar']   # but not 'myvar'
     assert lib.__name__ == repr(lib)
+    assert lib.__class__ is type(lib)
 
 def test_macro_var_callback():
     ffi = FFI()
@@ -1502,8 +1516,8 @@
         res = lib.bar(4, 5)
     assert res == 0
     assert f.getvalue() == (
-        "extern \"Python\": function bar() called, but no code was attached "
-        "to it yet with @ffi.def_extern().  Returning 0.\n")
+        b"extern \"Python\": function bar() called, but no code was attached "
+        b"to it yet with @ffi.def_extern().  Returning 0.\n")
 
     @ffi.def_extern("bar")
     def my_bar(x, y):
@@ -1520,10 +1534,10 @@
     baz1 = ffi.def_extern()(baz)
     assert baz1 is baz
     seen = []
-    baz(40L, 4L)
-    res = lib.baz(50L, 8L)
+    baz(long(40), long(4))
+    res = lib.baz(long(50), long(8))
     assert res is None
-    assert seen == [("Baz", 40L, 4L), ("Baz", 50, 8)]
+    assert seen == [("Baz", 40, 4), ("Baz", 50, 8)]
     assert type(seen[0][1]) is type(seen[0][2]) is long
     assert type(seen[1][1]) is type(seen[1][2]) is int
 
diff --git a/testing/support.py b/testing/support.py
--- a/testing/support.py
+++ b/testing/support.py
@@ -8,6 +8,7 @@
             return eval('u'+repr(other).replace(r'\\u', r'\u')
                                        .replace(r'\\U', r'\U'))
     u = U()
+    long = long     # for further "from testing.support import long"
     assert u+'a\x00b' == eval(r"u'a\x00b'")
     assert u+'a\u1234b' == eval(r"u'a\u1234b'")
     assert u+'a\U00012345b' == eval(r"u'a\U00012345b'")
@@ -22,9 +23,12 @@
 class StdErrCapture(object):
     """Capture writes to sys.stderr (not to the underlying file descriptor)."""
     def __enter__(self):
-        import StringIO
+        try:
+            from StringIO import StringIO
+        except ImportError:
+            from io import StringIO
         self.old_stderr = sys.stderr
-        sys.stderr = f = StringIO.StringIO()
+        sys.stderr = f = StringIO()
         return f
     def __exit__(self, *args):
         sys.stderr = self.old_stderr
@@ -35,6 +39,9 @@
     to the Posix manual."""
 
     def __init__(self, capture_fd=2):    # stderr by default
+        if sys.platform == 'win32':
+            import py
+            py.test.skip("seems not to work, too bad")
         self.capture_fd = capture_fd
 
     def __enter__(self):
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to