Author: Armin Rigo <[email protected]>
Branch: static-callback
Changeset: r2426:b5b5fb2182e7
Date: 2015-11-23 16:11 +0100
http://bitbucket.org/cffi/cffi/changeset/b5b5fb2182e7/

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
@@ -103,7 +103,11 @@
 #endif
 
 #if PY_MAJOR_VERSION < 3
-#define PyCapsule_New(pointer, name, destructor)        \
+# undef PyCapsule_GetPointer
+# undef PyCapsule_New
+# define PyCapsule_GetPointer(capsule, name) \
+    (PyCObject_AsVoidPtr(capsule))
+# define PyCapsule_New(pointer, name, destructor) \
     (PyCObject_FromVoidPtr(pointer, destructor))
 #endif
 
@@ -1607,7 +1611,7 @@
     PyObject_GC_UnTrack(cd);
 
     if (cd->c_type->ct_flags & CT_IS_VOID_PTR) {        /* a handle */
-        PyObject *x = (PyObject *)(cd->c_data + 42);
+        PyObject *x = ((CDataObject_own_structptr *)cd)->structobj;
         Py_DECREF(x);
     }
     else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) {   /* a callback */
@@ -1627,7 +1631,7 @@
 static int cdataowninggc_traverse(CDataObject *cd, visitproc visit, void *arg)
 {
     if (cd->c_type->ct_flags & CT_IS_VOID_PTR) {        /* a handle */
-        PyObject *x = (PyObject *)(cd->c_data + 42);
+        PyObject *x = ((CDataObject_own_structptr *)cd)->structobj;
         Py_VISIT(x);
     }
     else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) {   /* a callback */
@@ -1645,9 +1649,10 @@
 static int cdataowninggc_clear(CDataObject *cd)
 {
     if (cd->c_type->ct_flags & CT_IS_VOID_PTR) {        /* a handle */
-        PyObject *x = (PyObject *)(cd->c_data + 42);
+        CDataObject_own_structptr *cd1 = (CDataObject_own_structptr *)cd;
+        PyObject *x = cd1->structobj;
         Py_INCREF(Py_None);
-        cd->c_data = ((char *)Py_None) - 42;
+        cd1->structobj = Py_None;
         Py_DECREF(x);
     }
     else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) {   /* a callback */
@@ -1835,7 +1840,7 @@
 static PyObject *cdataowninggc_repr(CDataObject *cd)
 {
     if (cd->c_type->ct_flags & CT_IS_VOID_PTR) {        /* a handle */
-        PyObject *x = (PyObject *)(cd->c_data + 42);
+        PyObject *x = ((CDataObject_own_structptr *)cd)->structobj;
         return _cdata_repr2(cd, "handle to", x);
     }
     else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) {   /* a callback */
@@ -5649,10 +5654,26 @@
     return Py_None;
 }
 
+static PyObject *newp_handle(CTypeDescrObject *ct_voidp, PyObject *x)
+{
+    CDataObject_own_structptr *cd;
+    cd = (CDataObject_own_structptr 
*)PyObject_GC_New(CDataObject_own_structptr,
+                                                      &CDataOwningGC_Type);
+    if (cd == NULL)
+        return NULL;
+    Py_INCREF(ct_voidp);
+    cd->head.c_type = ct_voidp;
+    cd->head.c_data = (char *)cd;
+    cd->head.c_weakreflist = NULL;
+    Py_INCREF(x);
+    cd->structobj = x;
+    PyObject_GC_Track(cd);
+    return (PyObject *)cd;
+}
+
 static PyObject *b_newp_handle(PyObject *self, PyObject *args)
 {
     CTypeDescrObject *ct;
-    CDataObject *cd;
     PyObject *x;
     if (!PyArg_ParseTuple(args, "O!O", &CTypeDescr_Type, &ct, &x))
         return NULL;
@@ -5662,47 +5683,38 @@
         return NULL;
     }
 
-    cd = (CDataObject *)PyObject_GC_New(CDataObject, &CDataOwningGC_Type);
-    if (cd == NULL)
-        return NULL;
-    Py_INCREF(ct);
-    cd->c_type = ct;
-    Py_INCREF(x);
-    cd->c_data = ((char *)x) - 42;
-    cd->c_weakreflist = NULL;
-    PyObject_GC_Track(cd);
-    return (PyObject *)cd;
+    return newp_handle(ct, x);
 }
 
 static PyObject *b_from_handle(PyObject *self, PyObject *arg)
 {
     CTypeDescrObject *ct;
-    char *raw;
+    CDataObject_own_structptr *orgcd;
     PyObject *x;
     if (!CData_Check(arg)) {
         PyErr_SetString(PyExc_TypeError, "expected a 'cdata' object");
         return NULL;
     }
     ct = ((CDataObject *)arg)->c_type;
-    raw = ((CDataObject *)arg)->c_data;
     if (!(ct->ct_flags & CT_CAST_ANYTHING)) {
         PyErr_Format(PyExc_TypeError,
                      "expected a 'cdata' object with a 'void *' out of "
                      "new_handle(), got '%s'", ct->ct_name);
         return NULL;
     }
-    if (!raw) {
+    orgcd = (CDataObject_own_structptr *)((CDataObject *)arg)->c_data;
+    if (!orgcd) {
         PyErr_SetString(PyExc_RuntimeError,
                         "cannot use from_handle() on NULL pointer");
         return NULL;
     }
-    x = (PyObject *)(raw + 42);
-    if (Py_REFCNT(x) <= 0) {
+    if (Py_REFCNT(orgcd) <= 0 || Py_TYPE(orgcd) != &CDataOwningGC_Type) {
         Py_FatalError("ffi.from_handle() detected that the address passed "
                       "points to garbage. If it is really the result of "
                       "ffi.new_handle(), then the Python object has already "
                       "been garbage collected");
     }
+    x = orgcd->structobj;
     Py_INCREF(x);
     return x;
 }
diff --git a/c/ffi_obj.c b/c/ffi_obj.c
--- a/c/ffi_obj.c
+++ b/c/ffi_obj.c
@@ -24,6 +24,7 @@
 struct FFIObject_s {
     PyObject_HEAD
     PyObject *gc_wrefs, *gc_wrefs_freelist;
+    PyObject *init_once_cache;
     struct _cffi_parse_info_s info;
     char ctx_is_static, ctx_is_nonempty;
     builder_c_t types_builder;
@@ -52,6 +53,7 @@
     }
     ffi->gc_wrefs = NULL;
     ffi->gc_wrefs_freelist = NULL;
+    ffi->init_once_cache = NULL;
     ffi->info.ctx = &ffi->types_builder.ctx;
     ffi->info.output = internal_output;
     ffi->info.output_size = FFI_COMPLEXITY_OUTPUT;
@@ -65,6 +67,7 @@
     PyObject_GC_UnTrack(ffi);
     Py_XDECREF(ffi->gc_wrefs);
     Py_XDECREF(ffi->gc_wrefs_freelist);
+    Py_XDECREF(ffi->init_once_cache);
 
     free_builder_c(&ffi->types_builder, ffi->ctx_is_static);
 
@@ -676,18 +679,8 @@
 
 static PyObject *ffi_new_handle(FFIObject *self, PyObject *arg)
 {
-    CDataObject *cd;
-
-    cd = (CDataObject *)PyObject_GC_New(CDataObject, &CDataOwningGC_Type);
-    if (cd == NULL)
-        return NULL;
-    Py_INCREF(g_ct_voidp);     // <ctype 'void *'>
-    cd->c_type = g_ct_voidp;
-    Py_INCREF(arg);
-    cd->c_data = ((char *)arg) - 42;
-    cd->c_weakreflist = NULL;
-    PyObject_GC_Track(cd);
-    return (PyObject *)cd;
+    /* g_ct_voidp is equal to <ctype 'void *'> */
+    return newp_handle(g_ct_voidp, arg);
 }
 
 PyDoc_STRVAR(ffi_from_handle_doc,
@@ -696,32 +689,8 @@
 "cdata object returned by new_handle() is still alive (somewhere else\n"
 "in the program).  Failure to follow these rules will crash.");
 
-static PyObject *ffi_from_handle(PyObject *self, PyObject *arg)
-{
-    CTypeDescrObject *ct;
-    char *raw;
-    PyObject *x;
-    if (!CData_Check(arg)) {
-        PyErr_SetString(PyExc_TypeError, "expected a 'cdata' object");
-        return NULL;
-    }
-    ct = ((CDataObject *)arg)->c_type;
-    raw = ((CDataObject *)arg)->c_data;
-    if (!(ct->ct_flags & CT_CAST_ANYTHING)) {
-        PyErr_Format(PyExc_TypeError,
-                     "expected a 'cdata' object with a 'void *' out of "
-                     "new_handle(), got '%s'", ct->ct_name);
-        return NULL;
-    }
-    if (!raw) {
-        PyErr_SetString(PyExc_RuntimeError,
-                        "cannot use from_handle() on NULL pointer");
-        return NULL;
-    }
-    x = (PyObject *)(raw + 42);
-    Py_INCREF(x);
-    return x;
-}
+#define ffi_from_handle  b_from_handle   /* ffi_from_handle => b_from_handle
+                                            from _cffi_backend.c */
 
 PyDoc_STRVAR(ffi_from_buffer_doc,
 "Return a <cdata 'char[]'> that points to the data of the given Python\n"
@@ -916,6 +885,130 @@
 #define ffi_memmove  b_memmove     /* ffi_memmove() => b_memmove()
                                       from _cffi_backend.c */
 
+#ifdef WITH_THREAD
+# include "pythread.h"
+#else
+typedef void *PyThread_type_lock;
+# define PyThread_allocate_lock()        ((void *)-1)
+# define PyThread_free_lock(lock)        ((void)(lock))
+# define PyThread_acquire_lock(lock, _)  ((void)(lock))
+# define PyThread_release_lock(lock)     ((void)(lock))
+#endif
+
+PyDoc_STRVAR(ffi_init_once_doc,
+             "XXX document me");
+
+#if PY_MAJOR_VERSION < 3
+/* PyCapsule_New is redefined to be PyCObject_FromVoidPtr in _cffi_backend,
+   which gives 2.6 compatibility; but the destructor signature is different */
+static void _free_init_once_lock(void *lock)
+{
+    PyThread_free_lock((PyThread_type_lock)lock);
+}
+#else
+static void _free_init_once_lock(PyObject *capsule)
+{
+    PyThread_type_lock lock;
+    lock = PyCapsule_GetPointer(capsule, "cffi_init_once_lock");
+    if (lock != NULL)
+        PyThread_free_lock(lock);
+}
+#endif
+
+static PyObject *ffi_init_once(FFIObject *self, PyObject *args, PyObject *kwds)
+{
+    static char *keywords[] = {"func", "tag", NULL};
+    PyObject *cache, *func, *tag, *tup, *res, *x, *lockobj;
+    PyThread_type_lock lock;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", keywords, &func, &tag))
+        return NULL;
+
+    /* a lot of fun with reference counting and error checking
+       in this function */
+
+    /* atomically get or create a new dict (no GIL release) */
+    cache = self->init_once_cache;
+    if (cache == NULL) {
+        cache = PyDict_New();
+        if (cache == NULL)
+            return NULL;
+        self->init_once_cache = cache;
+    }
+
+    /* get the tuple from cache[tag], or make a new one: (False, lock) */
+    tup = PyDict_GetItem(cache, tag);
+    if (tup == NULL) {
+        lock = PyThread_allocate_lock();
+        if (lock == NULL)
+            return NULL;
+        x = PyCapsule_New(lock, "cffi_init_once_lock", _free_init_once_lock);
+        if (x == NULL) {
+            PyThread_free_lock(lock);
+            return NULL;
+        }
+        tup = PyTuple_Pack(2, Py_False, x);
+        Py_DECREF(x);
+        if (tup == NULL)
+            return NULL;
+        x = tup;
+
+        /* Possible corner case if 'tag' is an object overriding __eq__
+           in pure Python: the GIL may be released when we are running it.
+           We really need to call dict.setdefault(). */
+        tup = PyObject_CallMethod(cache, "setdefault", "OO", tag, x);
+        Py_DECREF(x);
+        if (tup == NULL)
+            return NULL;
+
+        Py_DECREF(tup);   /* there is still a ref inside the dict */
+    }
+
+    res = PyTuple_GET_ITEM(tup, 1);
+    Py_INCREF(res);
+
+    if (PyTuple_GET_ITEM(tup, 0) == Py_True) {
+        /* tup == (True, result): return the result. */
+        return res;
+    }
+
+    /* tup == (False, lock) */
+    lockobj = res;
+    lock = (PyThread_type_lock)PyCapsule_GetPointer(lockobj,
+                                                    "cffi_init_once_lock");
+    if (lock == NULL) {
+        Py_DECREF(lockobj);
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    PyThread_acquire_lock(lock, WAIT_LOCK);
+    Py_END_ALLOW_THREADS
+
+    x = PyDict_GetItem(cache, tag);
+    if (x != NULL && PyTuple_GET_ITEM(x, 0) == Py_True) {
+        /* the real result was put in the dict while we were waiting
+           for PyThread_acquire_lock() above */
+        res = PyTuple_GET_ITEM(x, 1);
+        Py_INCREF(res);
+    }
+    else {
+        res = PyObject_CallFunction(func, "");
+        if (res != NULL) {
+            tup = PyTuple_Pack(2, Py_True, res);
+            if (tup == NULL || PyDict_SetItem(cache, tag, tup) < 0) {
+                Py_XDECREF(tup);
+                Py_DECREF(res);
+                res = NULL;
+            }
+        }
+    }
+
+    PyThread_release_lock(lock);
+    Py_DECREF(lockobj);
+    return res;
+}
+
 
 #define METH_VKW  (METH_VARARGS | METH_KEYWORDS)
 static PyMethodDef ffi_methods[] = {
@@ -934,6 +1027,7 @@
 #ifdef MS_WIN32
  {"getwinerror",(PyCFunction)ffi_getwinerror,METH_VKW,     
ffi_getwinerror_doc},
 #endif
+ {"init_once",  (PyCFunction)ffi_init_once,  METH_VKW,     ffi_init_once_doc},
  {"integer_const",(PyCFunction)ffi_int_const,METH_VKW,     ffi_int_const_doc},
  {"memmove",    (PyCFunction)ffi_memmove,    METH_VKW,     ffi_memmove_doc},
  {"new",        (PyCFunction)ffi_new,        METH_VKW,     ffi_new_doc},
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -72,6 +72,7 @@
         self._cdefsources = []
         self._included_ffis = []
         self._windows_unicode = None
+        self._init_once_cache = {}
         if hasattr(backend, 'set_ffi'):
             backend.set_ffi(self)
         for name in backend.__dict__:
@@ -598,6 +599,30 @@
         return recompile(self, module_name, source, tmpdir=tmpdir,
                          source_extension=source_extension, **kwds)
 
+    def init_once(self, func, tag):
+        # Read _init_once_cache[tag], which is either (False, lock) if
+        # we're calling the function now in some thread, or (True, result).
+        # Don't call setdefault() in most cases, to avoid allocating and
+        # immediately freeing a lock; but still use setdefaut() to avoid
+        # races.
+        try:
+            x = self._init_once_cache[tag]
+        except KeyError:
+            x = self._init_once_cache.setdefault(tag, (False, allocate_lock()))
+        # Common case: we got (True, result), so we return the result.
+        if x[0]:
+            return x[1]
+        # Else, it's a lock.  Acquire it to serialize the following tests.
+        with x[1]:
+            # Read again from _init_once_cache the current status.
+            x = self._init_once_cache[tag]
+            if x[0]:
+                return x[1]
+            # Call the function and store the result back.
+            result = func()
+            self._init_once_cache[tag] = (True, result)
+        return result
+
 
 def _load_backend_lib(backend, name, flags):
     if name is None:
diff --git a/doc/source/using.rst b/doc/source/using.rst
--- a/doc/source/using.rst
+++ b/doc/source/using.rst
@@ -1046,16 +1046,18 @@
 *Calling ffi.from_handle(p) is invalid and will likely crash if
 the cdata object returned by new_handle() is not kept alive!*
 
-(In case you are wondering, this ``void *`` is not a ``PyObject *``
+(In case you are wondering, this ``void *`` is not the ``PyObject *``
 pointer.  This wouldn't make sense on PyPy anyway.)
 
 The ``ffi.new_handle()/from_handle()`` functions *conceptually* work
 like this:
 
-* ``new_handle()`` returns a cdata object that contains a reference to
-  the Python object; we call them collectively the "handle" cdata
-  objects.  The ``void *`` value in this handle cdata object is random
-  but unique.
+* ``new_handle()`` returns cdata objects that contains references to
+  the Python objects; we call them collectively the "handle" cdata
+  objects.  The ``void *`` value in these handle cdata objects are
+  random but unique.  *New in version 1.4:* two calls to
+  ``new_handle(x)`` are guaranteed to return cdata objects with
+  different ``void *`` values, even with the same ``x``.
 
 * ``from_handle(p)`` searches all live "handle" cdata objects for the
   one that has the same value ``p`` as its ``void *`` value.  It then
diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst
--- a/doc/source/whatsnew.rst
+++ b/doc/source/whatsnew.rst
@@ -3,6 +3,18 @@
 ======================
 
 
+v1.4.0
+======
+
+* ``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 different ``cdata``
+  objects with the same ``void *`` value.  This is useful to add and
+  remove handles from a global set without worrying about duplicates.
+
+* ``ffi.init_once()`` XXX
+
+
 v1.3.1
 ======
 
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
@@ -1809,3 +1809,35 @@
         assert lib.EE1 == 0
         assert lib.EE2 == 0
         assert lib.EE3 == 1
+
+    def test_init_once(self):
+        def do_init():
+            seen.append(1)
+            return 42
+        ffi = FFI()
+        seen = []
+        for i in range(3):
+            res = ffi.init_once(do_init, "tag1")
+            assert res == 42
+            assert seen == [1]
+        for i in range(3):
+            res = ffi.init_once(do_init, "tag2")
+            assert res == 42
+            assert seen == [1, 1]
+
+    def test_init_once_multithread(self):
+        import thread, time
+        def do_init():
+            seen.append('init!')
+            time.sleep(1)
+            seen.append('init done')
+            return 7
+        ffi = FFI()
+        seen = []
+        for i in range(6):
+            def f():
+                res = ffi.init_once(do_init, "tag")
+                seen.append(res)
+            thread.start_new_thread(f, ())
+        time.sleep(1.5)
+        assert seen == ['init!', 'init done'] + 6 * [7]
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,7 +464,7 @@
         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.maxint < 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:
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
@@ -193,6 +193,11 @@
     yp = ffi.new_handle([6, 4, 2])
     assert ffi.from_handle(yp) == [6, 4, 2]
 
+def test_handle_unique():
+    ffi = _cffi1_backend.FFI()
+    assert ffi.new_handle(None) is not ffi.new_handle(None)
+    assert ffi.new_handle(None) != ffi.new_handle(None)
+
 def test_ffi_cast():
     ffi = _cffi1_backend.FFI()
     assert ffi.cast("int(*)(int)", 0) == ffi.NULL
@@ -415,3 +420,37 @@
             assert int(ffi.cast("_Bool", ffi.cast(type, 42))) == 1
             assert int(ffi.cast("bool", ffi.cast(type, 42))) == 1
             assert int(ffi.cast("_Bool", ffi.cast(type, 0))) == 0
+
+def test_init_once():
+    def do_init():
+        seen.append(1)
+        return 42
+    ffi = _cffi1_backend.FFI()
+    seen = []
+    for i in range(3):
+        res = ffi.init_once(do_init, "tag1")
+        assert res == 42
+        assert seen == [1]
+    for i in range(3):
+        res = ffi.init_once(do_init, "tag2")
+        assert res == 42
+        assert seen == [1, 1]
+
+def test_init_once_multithread():
+    import thread, time
+    def do_init():
+        print 'init!'
+        seen.append('init!')
+        time.sleep(1)
+        seen.append('init done')
+        print 'init done'
+        return 7
+    ffi = _cffi1_backend.FFI()
+    seen = []
+    for i in range(6):
+        def f():
+            res = ffi.init_once(do_init, "tag")
+            seen.append(res)
+        thread.start_new_thread(f, ())
+    time.sleep(1.5)
+    assert seen == ['init!', 'init done'] + 6 * [7]
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to