summaryrefslogtreecommitdiff
path: root/tests/run/trashcan.pyx
blob: 29331fdf6c9a23b1ba5b8fd52b5765d27f751ec9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# mode: run

cimport cython


# Count number of times an object was deallocated twice. This should remain 0.
cdef int double_deallocations = 0
def assert_no_double_deallocations():
    global double_deallocations
    err = double_deallocations
    double_deallocations = 0
    assert not err


# Compute x = f(f(f(...(None)...))) nested n times and throw away the result.
# The real test happens when exiting this function: then a big recursive
# deallocation of x happens. We are testing two things in the tests below:
# that Python does not crash and that no double deallocation happens.
# See also https://github.com/python/cpython/pull/11841
def recursion_test(f, int n=2**20):
    x = None
    cdef int i
    for i in range(n):
        x = f(x)


@cython.trashcan(True)
cdef class Recurse:
    """
    >>> recursion_test(Recurse)
    >>> assert_no_double_deallocations()
    """
    cdef public attr
    cdef int deallocated

    def __cinit__(self, x):
        self.attr = x

    def __dealloc__(self):
        # Check that we're not being deallocated twice
        global double_deallocations
        double_deallocations += self.deallocated
        self.deallocated = 1


cdef class RecurseSub(Recurse):
    """
    >>> recursion_test(RecurseSub)
    >>> assert_no_double_deallocations()
    """
    cdef int subdeallocated

    def __dealloc__(self):
        # Check that we're not being deallocated twice
        global double_deallocations
        double_deallocations += self.subdeallocated
        self.subdeallocated = 1


@cython.freelist(4)
@cython.trashcan(True)
cdef class RecurseFreelist:
    """
    >>> recursion_test(RecurseFreelist)
    >>> recursion_test(RecurseFreelist, 1000)
    >>> assert_no_double_deallocations()
    """
    cdef public attr
    cdef int deallocated

    def __cinit__(self, x):
        self.attr = x

    def __dealloc__(self):
        # Check that we're not being deallocated twice
        global double_deallocations
        double_deallocations += self.deallocated
        self.deallocated = 1


# Subclass of list => uses trashcan by default
# As long as https://github.com/python/cpython/pull/11841 is not fixed,
# this does lead to double deallocations, so we skip that check.
cdef class RecurseList(list):
    """
    >>> RecurseList(42)
    [42]
    >>> recursion_test(RecurseList)
    """
    def __init__(self, x):
        super().__init__((x,))


# Some tests where the trashcan is NOT used. When the trashcan is not used
# in a big recursive deallocation, the __dealloc__s of the base classes are
# only run after the __dealloc__s of the subclasses.
# We use this to detect trashcan usage.
cdef int base_deallocated = 0
cdef int trashcan_used = 0
def assert_no_trashcan_used():
    global base_deallocated, trashcan_used
    err = trashcan_used
    trashcan_used = base_deallocated = 0
    assert not err


cdef class Base:
    def __dealloc__(self):
        global base_deallocated
        base_deallocated = 1


# Trashcan disabled by default
cdef class Sub1(Base):
    """
    >>> recursion_test(Sub1, 100)
    >>> assert_no_trashcan_used()
    """
    cdef public attr

    def __cinit__(self, x):
        self.attr = x

    def __dealloc__(self):
        global base_deallocated, trashcan_used
        trashcan_used += base_deallocated


@cython.trashcan(True)
cdef class Middle(Base):
    cdef public foo


# Trashcan disabled explicitly
@cython.trashcan(False)
cdef class Sub2(Middle):
    """
    >>> recursion_test(Sub2, 1000)
    >>> assert_no_trashcan_used()
    """
    cdef public attr

    def __cinit__(self, x):
        self.attr = x

    def __dealloc__(self):
        global base_deallocated, trashcan_used
        trashcan_used += base_deallocated