Author: Armin Rigo <ar...@tunes.org>
Branch: cpyext-avoid-roundtrip
Changeset: r93441:56643108f56a
Date: 2017-12-16 10:33 +0100
http://bitbucket.org/pypy/pypy/changeset/56643108f56a/

Log:    Test and fix for the case where state.C.Xxx calls directly some C
        function which tries to call back into RPython

diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -204,6 +204,9 @@
 # id.  Invariant: this variable always contain 0 when the PyPy GIL is
 # released.  It should also contain 0 when regular RPython code
 # executes.  In non-cpyext-related code, it will thus always be 0.
+# When cpyext-related C code runs, it contains the thread id (usually)
+# or the value -1 (only for state.C.PyXxx() functions which are short-
+# running and should not themselves release the GIL).
 #
 # **make_generic_cpy_call():** RPython to C, with the GIL held.  Before
 # the call, must assert that the global variable is 0 and set the
@@ -972,8 +975,14 @@
 
         # see "Handling of the GIL" above (careful, we don't have the GIL here)
         tid = rthread.get_or_make_ident()
-        _gil_auto = (gil_auto_workaround and cpyext_glob_tid_ptr[0] != tid)
-        if gil_acquire or _gil_auto:
+        _gil_auto = False
+        if gil_auto_workaround and cpyext_glob_tid_ptr[0] != tid:
+            # replace '-1' with the real tid, now that we have the tid
+            if cpyext_glob_tid_ptr[0] == -1:
+                cpyext_glob_tid_ptr[0] = tid
+            else:
+                _gil_auto = True
+        if _gil_auto or gil_acquire:
             if cpyext_glob_tid_ptr[0] == tid:
                 deadlock_error(pname)
             rgil.acquire()
diff --git a/pypy/module/cpyext/intobject.py b/pypy/module/cpyext/intobject.py
--- a/pypy/module/cpyext/intobject.py
+++ b/pypy/module/cpyext/intobject.py
@@ -35,7 +35,7 @@
         # value. However, it's just easier to call PyInt_FromLong with a dummy
         # value; make sure it's big enough to avoid the smallint optimization
         # (if it will ever be enabled)
-        return state.C.PyInt_FromLong(0x0DEADBEE)
+        return state.ccall("PyInt_FromLong", 0x0DEADBEE)
     else:
         return BaseCpyTypedescr.allocate(typedescr, space, w_type, itemcount)
 
diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py
--- a/pypy/module/cpyext/pyerrors.py
+++ b/pypy/module/cpyext/pyerrors.py
@@ -123,7 +123,9 @@
     error indicator."""
     raise oefmt(space.w_TypeError, "bad argument type for built-in operation")
 
-@cpython_api([], lltype.Void)
+# NB. this returns 'void' in CPython, but we can't easily, otherwise the
+# function is supposed not to fail
+@cpython_api([], rffi.INT_real, error=-1)
 def PyErr_BadInternalCall(space):
     raise oefmt(space.w_SystemError, "Bad internal call!")
 
diff --git a/pypy/module/cpyext/pyobject.py b/pypy/module/cpyext/pyobject.py
--- a/pypy/module/cpyext/pyobject.py
+++ b/pypy/module/cpyext/pyobject.py
@@ -307,7 +307,7 @@
     if w_obj is not None and space.type(w_obj) is space.w_int:
         state = space.fromcache(State)
         intval = space.int_w(w_obj)
-        return state.C.PyInt_FromLong(intval)
+        return state.ccall("PyInt_FromLong", intval)
     return get_pyobj_and_incref(space, w_obj, w_userdata, immortal=False)
 
 @specialize.ll()
diff --git a/pypy/module/cpyext/state.py b/pypy/module/cpyext/state.py
--- a/pypy/module/cpyext/state.py
+++ b/pypy/module/cpyext/state.py
@@ -1,4 +1,4 @@
-from rpython.rlib.objectmodel import we_are_translated
+from rpython.rlib.objectmodel import we_are_translated, specialize
 from rpython.rtyper.lltypesystem import rffi, lltype
 from pypy.interpreter.error import OperationError, oefmt
 from pypy.interpreter import executioncontext
@@ -45,6 +45,7 @@
         self.operror = None
         return operror
 
+    @specialize.arg(1)
     def check_and_raise_exception(self, always=False):
         operror = self.operror
         if operror:
@@ -168,6 +169,18 @@
         self.extensions[path] = w_copy
         return w_mod
 
+    @specialize.arg(1)
+    def ccall(self, name, *args):
+        from pypy.module.cpyext.api import cpyext_glob_tid_ptr
+        # This is similar to doing a direct call to state.C.PyXxx(), but
+        # must be used for any function that might potentially call back
+        # RPython code---most of them can, e.g. PyErr_NoMemory().
+        assert cpyext_glob_tid_ptr[0] == 0
+        cpyext_glob_tid_ptr[0] = -1
+        result = getattr(self.C, name)(*args)
+        cpyext_glob_tid_ptr[0] = 0
+        return result
+
 
 class CNamespace:
     def _freeze_(self):
diff --git a/pypy/module/cpyext/test/test_tupleobject.py 
b/pypy/module/cpyext/test/test_tupleobject.py
--- a/pypy/module/cpyext/test/test_tupleobject.py
+++ b/pypy/module/cpyext/test/test_tupleobject.py
@@ -144,6 +144,46 @@
                           space.newtuple([space.wrap(i) for i in range(3, 7)]))
 
 
+class AppTestBadInternalCall(AppTestCpythonExtensionBase):
+    def setup_method(self, meth):
+        from pypy.module.cpyext import tupleobject
+        if self.runappdirect:
+            py.test.skip("not for runappdirect")
+        AppTestCpythonExtensionBase.setup_method(self, meth)
+        tupleobject._BAD_ITEMCOUNT = 42
+
+    def teardown_method(self, meth):
+        from pypy.module.cpyext import tupleobject
+        AppTestCpythonExtensionBase.teardown_method(self, meth)
+        tupleobject._BAD_ITEMCOUNT = None
+
+    def test_badinternalcall_from_rpy(self):
+        # This used to hit "a thread is trying to wait for the GIL" in
+        # thread_gil.c
+        module = self.import_extension('foo', [
+            ("badinternalcall2", "METH_O",
+             """
+                Py_INCREF(Py_None);
+                return Py_None;
+             """),
+            ])
+        tup = (None,) * 42
+        raises(SystemError, module.badinternalcall2, tup)
+
+    def test_badinternalcall_from_rpy_with_threads(self):
+        # This used to cause a deadlock in thread_gil.c
+        module = self.import_extension('foo', [
+            ("badinternalcall2", "METH_O",
+             """
+                Py_INCREF(Py_None);
+                return Py_None;
+             """),
+            ])
+        import thread; thread.start_new_thread(lambda: None, ())
+        tup = (None,) * 42
+        raises(SystemError, module.badinternalcall2, tup)
+
+
 class AppTestTuple(AppTestCpythonExtensionBase):
     def test_refcounts(self):
         module = self.import_extension('foo', [
diff --git a/pypy/module/cpyext/tupleobject.py 
b/pypy/module/cpyext/tupleobject.py
--- a/pypy/module/cpyext/tupleobject.py
+++ b/pypy/module/cpyext/tupleobject.py
@@ -1,5 +1,6 @@
 from pypy.interpreter.error import oefmt
 from rpython.rtyper.lltypesystem import rffi, lltype
+from rpython.rlib.objectmodel import we_are_translated
 from rpython.rlib.debug import fatalerror_notb
 from pypy.module.cpyext.api import (
     cpython_api, Py_ssize_t, build_type_checkers_flags,
@@ -56,10 +57,17 @@
     return (w_type is space.w_tuple or
             space.issubtype_w(w_type, space.w_tuple))
 
+_BAD_ITEMCOUNT = None    # patched in test_badinternalcall_from_rpy
+
 def tuple_alloc(typedescr, space, w_type, itemcount):
     state = space.fromcache(State)
     if w_type is space.w_tuple:
-        return state.C.PyTuple_New(itemcount)
+        if not we_are_translated() and itemcount == _BAD_ITEMCOUNT:
+            itemcount = -42
+        ptup = state.ccall("PyTuple_New", itemcount)
+        if not ptup:
+            state.check_and_raise_exception(always=True)
+        return ptup
     else:
         return BaseCpyTypedescr.allocate(typedescr, space, w_type, itemcount)
 
@@ -116,7 +124,9 @@
 def tuple_from_args_w(space, args_w):
     state = space.fromcache(State)
     n = len(args_w)
-    py_tuple = state.C.PyTuple_New(n) # XXX: check for errors?
+    py_tuple = state.ccall("PyTuple_New", n)
+    if not py_tuple:
+        state.check_and_raise_exception(always=True)
     py_tuple = rffi.cast(PyTupleObject, py_tuple)
     for i, w_obj in enumerate(args_w):
         py_tuple.c_ob_item[i] = make_ref(space, w_obj)
@@ -181,7 +191,10 @@
         PyErr_BadInternalCall(space)
     oldref = rffi.cast(PyTupleObject, ref)
     oldsize = oldref.c_ob_size
-    p_ref[0] = state.C.PyTuple_New(newsize)
+    ptup = state.ccall("PyTuple_New", newsize)
+    if not ptup:
+        state.check_and_raise_exception(always=True)
+    p_ref[0] = ptup
     newref = rffi.cast(PyTupleObject, p_ref[0])
     try:
         if oldsize < newsize:
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to