@@ -9,18 +9,18 @@ Date: Thu Feb 14 10:02:41 2019 +0100
99 @cython.trashcan directive to enable the Python trashcan for deallocations
1010
1111diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py
12- index d5742de..27fcad6 100644
12+ index 56845330d..3a3e8a956 100644
1313--- a/Cython/Compiler/ModuleNode.py
1414+++ b/Cython/Compiler/ModuleNode.py
15- @@ -1426 ,6 +1426 ,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
15+ @@ -1443 ,6 +1443 ,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
1616
1717 is_final_type = scope.parent_type.is_final_type
1818 needs_gc = scope.needs_gc()
1919+ needs_trashcan = scope.needs_trashcan()
2020
2121 weakref_slot = scope.lookup_here("__weakref__") if not scope.is_closure_class_scope else None
2222 if weakref_slot not in scope.var_entries:
23- @@ -1464 ,6 +1465 ,11 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
23+ @@ -1481 ,6 +1482 ,11 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
2424 # running this destructor.
2525 code.putln("PyObject_GC_UnTrack(o);")
2626
@@ -32,7 +32,7 @@ index d5742de..27fcad6 100644
3232 # call the user's __dealloc__
3333 self.generate_usr_dealloc_call(scope, code)
3434
35- @@ -1537 ,6 +1543 ,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
35+ @@ -1554 ,6 +1560 ,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
3636 code.putln("(*Py_TYPE(o)->tp_free)(o);")
3737 if freelist_size:
3838 code.putln("}")
@@ -44,18 +44,20 @@ index d5742de..27fcad6 100644
4444 "}")
4545
4646diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py
47- index d859c19..19d96f1 100644
47+ index d03119fca..539629926 100644
4848--- a/Cython/Compiler/Options.py
4949+++ b/Cython/Compiler/Options.py
50- @@ -313,6 +313,7 @@ directive_types = {
50+ @@ -319,7 +319,8 @@ directive_types = {
5151 'freelist': int,
5252 'c_string_type': one_of('bytes', 'bytearray', 'str', 'unicode'),
5353 'c_string_encoding': normalise_encoding_name,
54+ - 'cpow': bool
55+ + 'cpow': bool,
5456+ 'trashcan': bool,
5557 }
5658
5759 for key, val in _directive_defaults.items():
58- @@ -355 ,6 +356 ,7 @@ directive_scopes = { # defaults to available everywhere
60+ @@ -362 ,6 +363 ,7 @@ directive_scopes = { # defaults to available everywhere
5961 'np_pythran': ('module',),
6062 'fast_gil': ('module',),
6163 'iterable_coroutine': ('module', 'function'),
@@ -64,18 +66,18 @@ index d859c19..19d96f1 100644
6466
6567
6668diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py
67- index 3b572d6..f200c5f 100644
69+ index c309bd04b..9231130b5 100644
6870--- a/Cython/Compiler/PyrexTypes.py
6971+++ b/Cython/Compiler/PyrexTypes.py
70- @@ -1136 ,6 +1136 ,7 @@ class PyObjectType(PyrexType):
72+ @@ -1129 ,6 +1129 ,7 @@ class PyObjectType(PyrexType):
7173 is_extern = False
7274 is_subclassed = False
7375 is_gc_simple = False
7476+ builtin_trashcan = False # builtin type using trashcan
7577
7678 def __str__(self):
7779 return "Python object"
78- @@ -1190 ,10 +1191 ,14 @@ class PyObjectType(PyrexType):
80+ @@ -1183 ,10 +1184 ,14 @@ class PyObjectType(PyrexType):
7981
8082
8183 builtin_types_that_cannot_create_refcycles = set([
@@ -91,7 +93,7 @@ index 3b572d6..f200c5f 100644
9193
9294 class BuiltinObjectType(PyObjectType):
9395 # objstruct_cname string Name of PyObject struct
94- @@ -1218 ,6 +1223 ,7 @@ class BuiltinObjectType(PyObjectType):
96+ @@ -1211 ,6 +1216 ,7 @@ class BuiltinObjectType(PyObjectType):
9597 self.typeptr_cname = "(&%s)" % cname
9698 self.objstruct_cname = objstruct_cname
9799 self.is_gc_simple = name in builtin_types_that_cannot_create_refcycles
@@ -100,10 +102,19 @@ index 3b572d6..f200c5f 100644
100102 # Special case the type type, as many C API calls (and other
101103 # libraries) actually expect a PyTypeObject* for type arguments.
102104diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py
103- index f7443cb..d44484d 100644
105+ index 7361a55ae..f0c311ba6 100644
104106--- a/Cython/Compiler/Symtab.py
105107+++ b/Cython/Compiler/Symtab.py
106- @@ -2085,6 +2085,22 @@ class CClassScope(ClassScope):
108+ @@ -2043,7 +2043,7 @@ class PyClassScope(ClassScope):
109+ class CClassScope(ClassScope):
110+ # Namespace of an extension type.
111+ #
112+ - # parent_type CClassType
113+ + # parent_type PyExtensionType
114+ # #typeobj_cname string or None
115+ # #objstruct_cname string
116+ # method_table_cname string
117+ @@ -2087,6 +2087,22 @@ class CClassScope(ClassScope):
107118 return not self.parent_type.is_gc_simple
108119 return False
109120
@@ -127,10 +138,10 @@ index f7443cb..d44484d 100644
127138 """
128139 Do we need to generate an implementation for the tp_clear slot? Can
129140diff --git a/Cython/Utility/ExtensionTypes.c b/Cython/Utility/ExtensionTypes.c
130- index 50d0e21..ca2adbe 100644
141+ index dc187ab49..f359165df 100644
131142--- a/Cython/Utility/ExtensionTypes.c
132143+++ b/Cython/Utility/ExtensionTypes.c
133- @@ -74 ,6 +74 ,54 @@ static int __Pyx_PyType_Ready(PyTypeObject *t) {
144+ @@ -119 ,6 +119 ,54 @@ static int __Pyx_PyType_Ready(PyTypeObject *t) {
134145 return r;
135146 }
136147
@@ -185,3 +196,160 @@ index 50d0e21..ca2adbe 100644
185196 /////////////// CallNextTpDealloc.proto ///////////////
186197
187198 static void __Pyx_call_next_tp_dealloc(PyObject* obj, destructor current_tp_dealloc);
199+ diff --git a/tests/run/trashcan.pyx b/tests/run/trashcan.pyx
200+ new file mode 100644
201+ index 000000000..93a501ff8
202+ --- /dev/null
203+ +++ b/tests/run/trashcan.pyx
204+ @@ -0,0 +1,148 @@
205+ + # mode: run
206+ +
207+ + cimport cython
208+ +
209+ +
210+ + # Count number of times an object was deallocated twice. This should remain 0.
211+ + cdef int double_deallocations = 0
212+ + def assert_no_double_deallocations():
213+ + global double_deallocations
214+ + err = double_deallocations
215+ + double_deallocations = 0
216+ + assert not err
217+ +
218+ +
219+ + # Compute x = f(f(f(...(None)...))) nested n times and throw away the result.
220+ + # The real test happens when exiting this function: then a big recursive
221+ + # deallocation of x happens. We are testing two things in the tests below:
222+ + # that Python does not crash and that no double deallocation happens.
223+ + # See also https://github.com/python/cpython/pull/11841
224+ + def recursion_test(f, int n=2**20):
225+ + x = None
226+ + cdef int i
227+ + for i in range(n):
228+ + x = f(x)
229+ +
230+ +
231+ + @cython.trashcan(True)
232+ + cdef class Recurse:
233+ + """
234+ + >>> recursion_test(Recurse)
235+ + >>> assert_no_double_deallocations()
236+ + """
237+ + cdef public attr
238+ + cdef int deallocated
239+ +
240+ + def __init__(self, x):
241+ + self.attr = x
242+ +
243+ + def __dealloc__(self):
244+ + # Check that we're not being deallocated twice
245+ + global double_deallocations
246+ + double_deallocations += self.deallocated
247+ + self.deallocated = 1
248+ +
249+ +
250+ + cdef class RecurseSub(Recurse):
251+ + """
252+ + >>> recursion_test(RecurseSub)
253+ + >>> assert_no_double_deallocations()
254+ + """
255+ + cdef int subdeallocated
256+ +
257+ + def __dealloc__(self):
258+ + # Check that we're not being deallocated twice
259+ + global double_deallocations
260+ + double_deallocations += self.subdeallocated
261+ + self.subdeallocated = 1
262+ +
263+ +
264+ + @cython.freelist(4)
265+ + @cython.trashcan(True)
266+ + cdef class RecurseFreelist:
267+ + """
268+ + >>> recursion_test(RecurseFreelist)
269+ + >>> recursion_test(RecurseFreelist, 1000)
270+ + >>> assert_no_double_deallocations()
271+ + """
272+ + cdef public attr
273+ + cdef int deallocated
274+ +
275+ + def __init__(self, x):
276+ + self.attr = x
277+ +
278+ + def __dealloc__(self):
279+ + # Check that we're not being deallocated twice
280+ + global double_deallocations
281+ + double_deallocations += self.deallocated
282+ + self.deallocated = 1
283+ +
284+ +
285+ + # Subclass of list => uses trashcan by default
286+ + # As long as https://github.com/python/cpython/pull/11841 is not fixed,
287+ + # this does lead to double deallocations, so we skip that check.
288+ + cdef class RecurseList(list):
289+ + """
290+ + >>> RecurseList(42)
291+ + [42]
292+ + >>> recursion_test(RecurseList)
293+ + """
294+ + def __init__(self, x):
295+ + super().__init__((x,))
296+ +
297+ +
298+ + # Some tests where the trashcan is NOT used. When the trashcan is not used
299+ + # in a big recursive deallocation, the __dealloc__s of the base classs are
300+ + # only run after the __dealloc__s of the subclasses.
301+ + # We use this to detect trashcan usage.
302+ + cdef int base_deallocated = 0
303+ + cdef int trashcan_used = 0
304+ + def assert_no_trashcan_used():
305+ + global base_deallocated, trashcan_used
306+ + err = trashcan_used
307+ + trashcan_used = base_deallocated = 0
308+ + assert not err
309+ +
310+ +
311+ + cdef class Base:
312+ + def __dealloc__(self):
313+ + global base_deallocated
314+ + base_deallocated = 1
315+ +
316+ +
317+ + # Trashcan disabled by default
318+ + cdef class Sub1(Base):
319+ + """
320+ + >>> recursion_test(Sub1, 100)
321+ + >>> assert_no_trashcan_used()
322+ + """
323+ + cdef public attr
324+ +
325+ + def __init__(self, x):
326+ + self.attr = x
327+ +
328+ + def __dealloc__(self):
329+ + global base_deallocated, trashcan_used
330+ + trashcan_used += base_deallocated
331+ +
332+ +
333+ + @cython.trashcan(True)
334+ + cdef class Middle(Base):
335+ + cdef public foo
336+ +
337+ +
338+ + # Trashcan disabled explicitly
339+ + @cython.trashcan(False)
340+ + cdef class Sub2(Middle):
341+ + """
342+ + >>> recursion_test(Sub2, 1000)
343+ + >>> assert_no_trashcan_used()
344+ + """
345+ + cdef public attr
346+ +
347+ + def __init__(self, x):
348+ + self.attr = x
349+ +
350+ + def __dealloc__(self):
351+ + global base_deallocated, trashcan_used
352+ + trashcan_used += base_deallocated
353+ - -
354+ 2.37.1 (Apple Git-137.1)
355+
0 commit comments