[Cython] memoryview refcount error

Syam Gadde syam.gadde at duke.edu
Fri Jan 31 20:11:49 CET 2014


I've done some more digging here. It turns out the test case for this 
bug is simpler than I thought:

import numpy
cimport numpy

def testfunc():
    a = numpy.arange(12).reshape([3,4])
    cdef numpy.int_t[:,:] a_view = a
    cdef numpy.int_t[:,:] b = a_view[:,:].T

testfunc()


The core issue is that a transpose of a slice of a memoryview results in 
bad reference counts on the memview (comments interspersed below to 
indicate refcount and who has references to the base memoryview):

   /* "testtranspose.pyx":6
  * def testfunc():
  *    a = numpy.arange(12).reshape([3,4])
  *    cdef numpy.int_t[:,:] a_view = a             # <<<<<<<<<<<<<<
  *    cdef numpy.int_t[:,:] b = a_view[:,:].T
  *
  */
   __pyx_t_4 = 
__Pyx_PyObject_to_MemoryviewSlice_dsds_nn___pyx_t_5numpy_int_t(__pyx_v_a);
   if (unlikely(!__pyx_t_4.memview)) {__pyx_filename = __pyx_f[0]; 
__pyx_lineno = 6; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
   __pyx_v_a_view = __pyx_t_4;
   __pyx_t_4.memview = NULL;
   __pyx_t_4.data = NULL;
// >>> [SG] REFCOUNT=1 (pointed to by __pyx_v_a_view)


   /* "testtranspose.pyx":7
  *    a = numpy.arange(12).reshape([3,4])
  *    cdef numpy.int_t[:,:] a_view = a
  *    cdef numpy.int_t[:,:] b = a_view[:,:].T # <<<<<<<<<<<<<<
  *
  * testfunc()
  */
   __pyx_t_6 = -1;
   __pyx_t_5.data = __pyx_v_a_view.data;
   __pyx_t_5.memview = __pyx_v_a_view.memview;
   __PYX_INC_MEMVIEW(&__pyx_t_5, 0);
// >>> [SG] REFCOUNT=2 (pointed to by __pyx_v_a_view, __pyx_t_5)
   __pyx_t_5.shape[0] = __pyx_v_a_view.shape[0];
__pyx_t_5.strides[0] = __pyx_v_a_view.strides[0];
     __pyx_t_5.suboffsets[0] = -1;

__pyx_t_5.shape[1] = __pyx_v_a_view.shape[1];
__pyx_t_5.strides[1] = __pyx_v_a_view.strides[1];
     __pyx_t_5.suboffsets[1] = -1;

__pyx_t_7 = __pyx_t_5;
// >>> [SG] REFCOUNT=2 (pointed to by __pyx_v_a_view, __pyx_t_5, __pyx_t_7)
   if (unlikely(__pyx_memslice_transpose(&__pyx_t_7) == 0)) 
{__pyx_filename = __pyx_f[0]; __pyx_lineno = 7; __pyx_clineno = 
__LINE__; goto __pyx_L1_error;}
   __PYX_XDEC_MEMVIEW(&__pyx_t_5, 1);
// >>> [SG] REFCOUNT=1 (pointed to by __pyx_v_a_view, __pyx_t_5, __pyx_t_7)
   __pyx_v_b = __pyx_t_7;
// >>> [SG] REFCOUNT=1 (pointed to by __pyx_v_a_view, __pyx_t_5, 
__pyx_t_7, __pyx_v_b)
   __pyx_t_7.memview = NULL;
   __pyx_t_7.data = NULL;
// >>> [SG] REFCOUNT=1 (pointed to by __pyx_v_a_view, __pyx_t_5, __pyx_v_b)

Note that now __pyx_v_a_view, __pyx_t_b and __pyx_t_5 retain references 
to the same memview, but the memview's reference count is only 1.  At 
the end of the function, the memviews of both __pyx_v_a_view and 
__pyx_v_b will both get decremented, causing a segfault.  If any error 
happens before that, then it will also attempt a decrement of __pyx_t_5 
(which is really, really annoying because the resulting segfault loses 
the actual error).  I think __pyx_t_7 needs an increment before the call 
to memslice_transpose, and that __pyx_t_5.memview needs to be set to 
NULL after its count is decremented.  The following patch fixes the 
issue, but as I'm not very familiar with Cython internals, it may be 
doing too much, or doing it in the wrong place.

-syam


diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index 315d6bb..631db8b 100644
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -614,6 +614,8 @@ class ExprNode(Node):
                  elif self.type.is_memoryviewslice:
                      code.put_xdecref_memoryviewslice(
                              self.result(), have_gil=not 
self.in_nogil_context)
+                    code.putln("%s.memview = NULL;" % self.result())
+                    code.putln("%s.data = NULL;" % self.result())
          else:
              # Already done if self.is_temp
              self.generate_subexpr_disposal_code(code)
@@ -5561,9 +5563,10 @@ class AttributeNode(ExprNode):
                          return

                  code.putln("%s = %s;" % (self.result(), 
self.obj.result()))
-                if self.obj.is_name or (self.obj.is_attribute and
- self.obj.is_memslice_transpose):
- code.put_incref_memoryviewslice(self.result(), have_gil=True)
+                #if self.obj.is_name or (self.obj.is_attribute and
+                # self.obj.is_memslice_transpose):
+                # code.put_incref_memoryviewslice(self.result(), 
have_gil=True)
+                code.put_incref_memoryviewslice(self.result(), 
have_gil=True)

                  T = "__pyx_memslice_transpose(&%s) == 0"
                  code.putln(code.error_goto_if(T % self.result(), 
self.pos))
@@ -5586,10 +5589,14 @@ class AttributeNode(ExprNode):
      def generate_disposal_code(self, code):
          if self.is_temp and self.type.is_memoryviewslice and 
self.is_memslice_transpose:
              # mirror condition for putting the memview incref here:
-            if self.obj.is_name or (self.obj.is_attribute and
- self.obj.is_memslice_transpose):
-                code.put_xdecref_memoryviewslice(
+            #if self.obj.is_name or (self.obj.is_attribute and
+            # self.obj.is_memslice_transpose):
+            #    code.put_xdecref_memoryviewslice(
+            #            self.result(), have_gil=True)
+            code.put_xdecref_memoryviewslice(
                          self.result(), have_gil=True)
+            code.putln("%s.memview = NULL;" % self.result())
+            code.putln("%s.data = NULL;" % self.result())
          else:
              ExprNode.generate_disposal_code(self, code)



On 01/28/2014 01:47 PM, Syam Gadde wrote:
> Hi again,
>
> I'm exploring the typed memoryview feature with numpy and encountered a
> refcount error.  It seems to happen when I create a slice of a
> memoryview, and transpose it.  If I don't do both, I don't get the
> refcount error.  It happens in the error cleanup code.  I suspect that
> any error that causes the function to jump to the error cleanup code
> will do the same thing as the Exception below -- but that seemed the
> easiest way to demonstrate the problem. However, if there truly is an
> error in my code, please let me know! Thanks,
>
> -syam
>
> # BEGIN CODE
> import numpy
> cimport numpy
>
> class MyException(Exception):
>       def __init__(self):
>           Exception.__init__(self)
>
> def testfunc():
>      a = numpy.arange(12).reshape([3,4])
>
>      cdef numpy.int_t[:,:] a_view = a
>
>      ## here is a slice followed by a transpose
>      cdef numpy.int_t[:,:] b = a_view[:,:].T
>      ## same thing happens with a more realistic slicing:
>      #cdef numpy.int_t[:,:] b = a_view[1:2,:].T
>      ## however, there is no error if I do this instead:
>      #cdef numpy.int_t[:,:] b = a_view.T
>      ## also no error if I do this instead:
>      #cdef numpy.int_t[:,:] b = a_view[:,:]
>
>      # The exception below is just to force the function to abort
>      # and run the extraneous __PYX_XDEC_MEMVIEW in the error
>      # cleanup code:
>      #   Fatal Python error: Acquisition count is 0 (line XXXX)
>      # Comment out this exception and we don't get the error
>      raise MyException()
>
> try:
>       testfunc()
> except MyException:
>       pass
> # END CODE
>
>
>
> _______________________________________________
> cython-devel mailing list
> cython-devel at python.org
> https://mail.python.org/mailman/listinfo/cython-devel
>



More information about the cython-devel mailing list