summaryrefslogtreecommitdiffstats
path: root/lib/greenlet/tests/test_gc.py
blob: a2a41cab400c6b16807d9ca9f55431496ddcdcdb (plain) (blame)
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
import gc
import sys
import unittest
import weakref

import greenlet


class GCTests(unittest.TestCase):
    def test_dead_circular_ref(self):
        o = weakref.ref(greenlet.greenlet(greenlet.getcurrent).switch())
        gc.collect()
        self.assertTrue(o() is None)
        self.assertFalse(gc.garbage, gc.garbage)

    if greenlet.GREENLET_USE_GC:
        # These only work with greenlet gc support

        def test_circular_greenlet(self):
            class circular_greenlet(greenlet.greenlet):
                pass
            o = circular_greenlet()
            o.self = o
            o = weakref.ref(o)
            gc.collect()
            self.assertTrue(o() is None)
            self.assertFalse(gc.garbage, gc.garbage)

        def test_inactive_ref(self):
            class inactive_greenlet(greenlet.greenlet):
                def __init__(self):
                    greenlet.greenlet.__init__(self, run=self.run)

                def run(self):
                    pass
            o = inactive_greenlet()
            o = weakref.ref(o)
            gc.collect()
            self.assertTrue(o() is None)
            self.assertFalse(gc.garbage, gc.garbage)

        def test_finalizer_crash(self):
            # This test is designed to crash when active greenlets
            # are made garbage collectable, until the underlying
            # problem is resolved. How does it work:
            # - order of object creation is important
            # - array is created first, so it is moved to unreachable first
            # - we create a cycle between a greenlet and this array
            # - we create an object that participates in gc, is only
            #   referenced by a greenlet, and would corrupt gc lists
            #   on destruction, the easiest is to use an object with
            #   a finalizer
            # - because array is the first object in unreachable it is
            #   cleared first, which causes all references to greenlet
            #   to disappear and causes greenlet to be destroyed, but since
            #   it is still live it causes a switch during gc, which causes
            #   an object with finalizer to be destroyed, which causes stack
            #   corruption and then a crash
            class object_with_finalizer(object):
                def __del__(self):
                    pass
            array = []
            parent = greenlet.getcurrent()
            def greenlet_body():
                greenlet.getcurrent().object = object_with_finalizer()
                try:
                    parent.switch()
                finally:
                    del greenlet.getcurrent().object
            g = greenlet.greenlet(greenlet_body)
            g.array = array
            array.append(g)
            g.switch()
            del array
            del g
            greenlet.getcurrent()
            gc.collect()