summaryrefslogtreecommitdiff
path: root/tests/run/no_gc_clear.pyx
blob: 643ec4c6ed50a92109b98a38d1b2c8e18a869564 (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
"""
Check that the @cython.no_gc_clear decorator disables generation of the
tp_clear slot so that __dealloc__ will still see the original reference
contents.

Discussed here: http://article.gmane.org/gmane.comp.python.cython.devel/14986
"""

cimport cython
from cpython.ref cimport PyObject, Py_TYPE

# Pull tp_clear for PyTypeObject as I did not find another way to access it
# from Cython code.

cdef extern from *:
    ctypedef struct PyTypeObject:
        void (*tp_clear)(object)

    ctypedef struct __pyx_CyFunctionObject:
        PyObject* func_closure


def is_tp_clear_null(obj):
    return (<PyTypeObject*>Py_TYPE(obj)).tp_clear is NULL


def is_closure_tp_clear_null(func):
    return is_tp_clear_null(
        <object>(<__pyx_CyFunctionObject*>func).func_closure)


@cython.no_gc_clear
cdef class DisableTpClear:
    """
    An extension type that has a tp_clear method generated to test that it
    actually clears the references to NULL.

    >>> uut = DisableTpClear()
    >>> is_tp_clear_null(uut)
    True
    >>> uut.call_tp_clear()
    >>> type(uut.requires_cleanup) == list
    True
    >>> del uut
    """

    cdef public object requires_cleanup

    def __cinit__(self):
        self.requires_cleanup = [
                "Some object that needs cleaning in __dealloc__"]

    def call_tp_clear(self):
        cdef PyTypeObject *pto = Py_TYPE(self)
        if pto.tp_clear != NULL:
            pto.tp_clear(self)


cdef class ReallowTpClear(DisableTpClear):
    """
    >>> import gc
    >>> obj = ReallowTpClear()
    >>> is_tp_clear_null(obj)
    False

    >>> obj.attr = obj  # create hard reference cycle
    >>> del obj; _ignore = gc.collect()

    # Problem: cannot really validate that the cycle was cleaned up without using weakrefs etc...
    """
    cdef public object attr


def test_closure_without_clear(str x):
    """
    >>> c = test_closure_without_clear('abc')
    >>> is_tp_clear_null(c)
    False
    >>> is_closure_tp_clear_null(c)
    True
    >>> c('cba')
    'abcxyzcba'
    """
    def c(str s):
        return x + 'xyz' + s
    return c


def test_closure_with_clear(list x):
    """
    >>> c = test_closure_with_clear(list('abc'))
    >>> is_tp_clear_null(c)
    False
    >>> is_closure_tp_clear_null(c)
    False
    >>> c('cba')
    'abcxyzcba'
    """
    def c(str s):
        return ''.join(x) + 'xyz' + s
    return c