1# mode: run 2 3cimport cython 4 5 6# Count number of times an object was deallocated twice. This should remain 0. 7cdef int double_deallocations = 0 8def assert_no_double_deallocations(): 9 global double_deallocations 10 err = double_deallocations 11 double_deallocations = 0 12 assert not err 13 14 15# Compute x = f(f(f(...(None)...))) nested n times and throw away the result. 16# The real test happens when exiting this function: then a big recursive 17# deallocation of x happens. We are testing two things in the tests below: 18# that Python does not crash and that no double deallocation happens. 19# See also https://github.com/python/cpython/pull/11841 20def recursion_test(f, int n=2**20): 21 x = None 22 cdef int i 23 for i in range(n): 24 x = f(x) 25 26 27@cython.trashcan(True) 28cdef class Recurse: 29 """ 30 >>> recursion_test(Recurse) 31 >>> assert_no_double_deallocations() 32 """ 33 cdef public attr 34 cdef int deallocated 35 36 def __cinit__(self, x): 37 self.attr = x 38 39 def __dealloc__(self): 40 # Check that we're not being deallocated twice 41 global double_deallocations 42 double_deallocations += self.deallocated 43 self.deallocated = 1 44 45 46cdef class RecurseSub(Recurse): 47 """ 48 >>> recursion_test(RecurseSub) 49 >>> assert_no_double_deallocations() 50 """ 51 cdef int subdeallocated 52 53 def __dealloc__(self): 54 # Check that we're not being deallocated twice 55 global double_deallocations 56 double_deallocations += self.subdeallocated 57 self.subdeallocated = 1 58 59 60@cython.freelist(4) 61@cython.trashcan(True) 62cdef class RecurseFreelist: 63 """ 64 >>> recursion_test(RecurseFreelist) 65 >>> recursion_test(RecurseFreelist, 1000) 66 >>> assert_no_double_deallocations() 67 """ 68 cdef public attr 69 cdef int deallocated 70 71 def __cinit__(self, x): 72 self.attr = x 73 74 def __dealloc__(self): 75 # Check that we're not being deallocated twice 76 global double_deallocations 77 double_deallocations += self.deallocated 78 self.deallocated = 1 79 80 81# Subclass of list => uses trashcan by default 82# As long as https://github.com/python/cpython/pull/11841 is not fixed, 83# this does lead to double deallocations, so we skip that check. 84cdef class RecurseList(list): 85 """ 86 >>> RecurseList(42) 87 [42] 88 >>> recursion_test(RecurseList) 89 """ 90 def __init__(self, x): 91 super().__init__((x,)) 92 93 94# Some tests where the trashcan is NOT used. When the trashcan is not used 95# in a big recursive deallocation, the __dealloc__s of the base classes are 96# only run after the __dealloc__s of the subclasses. 97# We use this to detect trashcan usage. 98cdef int base_deallocated = 0 99cdef int trashcan_used = 0 100def assert_no_trashcan_used(): 101 global base_deallocated, trashcan_used 102 err = trashcan_used 103 trashcan_used = base_deallocated = 0 104 assert not err 105 106 107cdef class Base: 108 def __dealloc__(self): 109 global base_deallocated 110 base_deallocated = 1 111 112 113# Trashcan disabled by default 114cdef class Sub1(Base): 115 """ 116 >>> recursion_test(Sub1, 100) 117 >>> assert_no_trashcan_used() 118 """ 119 cdef public attr 120 121 def __cinit__(self, x): 122 self.attr = x 123 124 def __dealloc__(self): 125 global base_deallocated, trashcan_used 126 trashcan_used += base_deallocated 127 128 129@cython.trashcan(True) 130cdef class Middle(Base): 131 cdef public foo 132 133 134# Trashcan disabled explicitly 135@cython.trashcan(False) 136cdef class Sub2(Middle): 137 """ 138 >>> recursion_test(Sub2, 1000) 139 >>> assert_no_trashcan_used() 140 """ 141 cdef public attr 142 143 def __cinit__(self, x): 144 self.attr = x 145 146 def __dealloc__(self): 147 global base_deallocated, trashcan_used 148 trashcan_used += base_deallocated 149