Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
test_greenlet.py1427 linesDownload Raw Back to tests
1import gc
2import sys
3import time
4import threading
5import unittest
6
7from abc import ABCMeta
8from abc import abstractmethod
9
10import greenlet
11from greenlet import greenlet as RawGreenlet
12from . import TestCase
13from . import RUNNING_ON_MANYLINUX
14from . import PY313
15from . import PY314
16from . import RUNNING_ON_FREETHREAD_BUILD
17from .leakcheck import fails_leakcheck
18from .leakcheck import ignores_leakcheck
19
20
21# We manually manage locks in many tests
22# pylint:disable=consider-using-with
23# pylint:disable=too-many-public-methods
24# This module is quite large.
25# TODO: Refactor into separate test files. For example,
26# put all the regression tests that used to produce
27# crashes in test_greenlet_no_crash; put tests that DO deliberately crash
28# the interpreter into test_greenlet_crash.
29# pylint:disable=too-many-lines
30
31class SomeError(Exception):
32    pass
33
34
35def fmain(seen):
36    try:
37        greenlet.getcurrent().parent.switch()
38    except:
39        seen.append(sys.exc_info()[0])
40        raise
41    raise SomeError
42
43
44def send_exception(g, exc):
45    # note: send_exception(g, exc)  can be now done with  g.throw(exc).
46    # the purpose of this test is to explicitly check the propagation rules.
47    def crasher(exc):
48        raise exc
49    g1 = RawGreenlet(crasher, parent=g)
50    g1.switch(exc)
51
52
53class TestGreenlet(TestCase):
54
55    def _do_simple_test(self):
56        lst = []
57
58        def f():
59            lst.append(1)
60            greenlet.getcurrent().parent.switch()
61            lst.append(3)
62        g = RawGreenlet(f)
63        lst.append(0)
64        g.switch()
65        lst.append(2)
66        g.switch()
67        lst.append(4)
68        self.assertEqual(lst, list(range(5)))
69
70    def test_simple(self):
71        self._do_simple_test()
72
73    def test_switch_no_run_raises_AttributeError(self):
74        g = RawGreenlet()
75        with self.assertRaises(AttributeError) as exc:
76            g.switch()
77
78        self.assertIn("run", str(exc.exception))
79
80    def test_throw_no_run_raises_AttributeError(self):
81        g = RawGreenlet()
82        with self.assertRaises(AttributeError) as exc:
83            g.throw(SomeError)
84
85        self.assertIn("run", str(exc.exception))
86
87    def test_parent_equals_None(self):
88        g = RawGreenlet(parent=None)
89        self.assertIsNotNone(g)
90        self.assertIs(g.parent, greenlet.getcurrent())
91
92    def test_run_equals_None(self):
93        g = RawGreenlet(run=None)
94        self.assertIsNotNone(g)
95        self.assertIsNone(g.run)
96
97    def test_two_children(self):
98        lst = []
99
100        def f():
101            lst.append(1)
102            greenlet.getcurrent().parent.switch()
103            lst.extend([1, 1])
104        g = RawGreenlet(f)
105        h = RawGreenlet(f)
106        g.switch()
107        self.assertEqual(len(lst), 1)
108        h.switch()
109        self.assertEqual(len(lst), 2)
110        h.switch()
111        self.assertEqual(len(lst), 4)
112        self.assertEqual(h.dead, True)
113        g.switch()
114        self.assertEqual(len(lst), 6)
115        self.assertEqual(g.dead, True)
116
117    def test_two_recursive_children(self):
118        lst = []
119
120        def f():
121            lst.append('b')
122            greenlet.getcurrent().parent.switch()
123
124        def g():
125            lst.append('a')
126            g = RawGreenlet(f)
127            g.switch()
128            lst.append('c')
129        self.assertEqual(sys.getrefcount(g), 2 if not PY314 else 1)
130        g = RawGreenlet(g)
131        # Python 3.14 elides reference counting operations
132        # in some cases. See https://github.com/python/cpython/pull/130708
133        self.assertEqual(sys.getrefcount(g), 2 if not PY314 else 1)
134        g.switch()
135        self.assertEqual(lst, ['a', 'b', 'c'])
136        # Just the one in this frame, plus the one on the stack we pass to the function
137        self.assertEqual(sys.getrefcount(g), 2 if not PY314 else 1)
138
139    def test_threads(self):
140        success = []
141
142        def f():
143            self._do_simple_test()
144            success.append(True)
145        ths = [threading.Thread(target=f) for i in range(10)]
146        for th in ths:
147            th.start()
148        for th in ths:
149            th.join(10)
150        self.assertEqual(len(success), len(ths))
151
152    @ignores_leakcheck
153    def test_switching_many_threads(self):
154        # This can expose issues on the free-threaded build.
155        def creates_greenlet(greenlets, wait_to_die_until):
156            def body():
157                while True:
158                    greenlet.getcurrent().parent.switch()
159            g = greenlet.greenlet(body)
160            g.switch()
161            greenlets.append(g)
162            wait_to_die_until.wait()
163
164        def switches_from_other_thread(greenlets, wait_to_check_until, quit_after):
165            wait_to_check_until.wait()
166            while not quit_after.is_set():
167                for g in list(greenlets):
168                    try:
169                        if not g.dead:
170                            g.switch()
171                    except greenlet.error:
172                        # Every attempt where the greenlet isn't dead should
173                        # raise this
174                        pass
175
176        def run_it(thread_count=16):
177            greenlets = []
178            greenlets_all_created = threading.Event()
179            all_threads_dead = threading.Event()
180
181            creators = [
182                threading.Thread(target=creates_greenlet, args=(greenlets,
183                                                                greenlets_all_created))
184                for _
185                in range(thread_count)
186            ]
187
188            switchers = [
189                threading.Thread(target=switches_from_other_thread,
190                                 args=(greenlets, greenlets_all_created,
191                                       all_threads_dead))
192                for _
193                in range(thread_count)
194            ]
195
196            for t in (creators + switchers):
197                t.start()
198
199            # Simple polling loop
200            while len(greenlets) < thread_count:
201                time.sleep(0.0001)
202            greenlets_all_created.set()
203
204            for t in creators:
205                t.join(1.0)
206            all_threads_dead.set()
207            for t in switchers:
208                t.join(1.0)
209
210        # enough reps to have a chance of repeating, not so many it
211        # takes forever
212        for _ in range(20):
213            run_it()
214            gc.collect()
215
216    def test_exception(self):
217        seen = []
218        g1 = RawGreenlet(fmain)
219        g2 = RawGreenlet(fmain)
220        g1.switch(seen)
221        g2.switch(seen)
222        g2.parent = g1
223
224        self.assertEqual(seen, [])
225        #with self.assertRaises(SomeError):
226        #    p("***Switching back")
227        #    g2.switch()
228        # Creating this as a bound method can reveal bugs that
229        # are hidden on newer versions of Python that avoid creating
230        # bound methods for direct expressions; IOW, don't use the `with`
231        # form!
232        self.assertRaises(SomeError, g2.switch)
233        self.assertEqual(seen, [SomeError])
234
235        value = g2.switch()
236        self.assertEqual(value, ())
237        self.assertEqual(seen, [SomeError])
238
239        value = g2.switch(25)
240        self.assertEqual(value, 25)
241        self.assertEqual(seen, [SomeError])
242
243
244    def test_send_exception(self):
245        seen = []
246        g1 = RawGreenlet(fmain)
247        g1.switch(seen)
248        self.assertRaises(KeyError, send_exception, g1, KeyError)
249        self.assertEqual(seen, [KeyError])
250
251    def test_dealloc(self):
252        seen = []
253        g1 = RawGreenlet(fmain)
254        g2 = RawGreenlet(fmain)
255        g1.switch(seen)
256        g2.switch(seen)
257        self.assertEqual(seen, [])
258        del g1
259        gc.collect()
260        self.assertEqual(seen, [greenlet.GreenletExit])
261        del g2
262        gc.collect()
263        self.assertEqual(seen, [greenlet.GreenletExit, greenlet.GreenletExit])
264
265    def test_dealloc_catches_GreenletExit_throws_other(self):
266        def run():
267            try:
268                greenlet.getcurrent().parent.switch()
269            except greenlet.GreenletExit:
270                raise SomeError from None
271
272        g = RawGreenlet(run)
273        g.switch()
274
275        unraisable_events = []
276        old_hook = sys.unraisablehook
277        def _capture(unraisable):
278            unraisable_events.append(unraisable)
279        sys.unraisablehook = _capture
280        try:
281            del g
282        finally:
283            sys.unraisablehook = old_hook
284
285        self.assertEqual(len(unraisable_events), 1)
286        self.assertIsInstance(unraisable_events[0].exc_value, SomeError)
287
288
289    @unittest.skipIf(
290        PY313 and RUNNING_ON_MANYLINUX,
291        "Sometimes flaky (getting one GreenletExit in the second list)"
292        # Probably due to funky timing interactions?
293        # TODO: FIXME Make that work.
294    )
295
296    def test_dealloc_other_thread(self):
297        seen = []
298        someref = []
299
300        bg_glet_created_running_and_no_longer_ref_in_bg = threading.Event()
301        fg_ref_released = threading.Event()
302        bg_should_be_clear = threading.Event()
303        ok_to_exit_bg_thread = threading.Event()
304
305        def f():
306            g1 = RawGreenlet(fmain)
307            g1.switch(seen)
308            someref.append(g1)
309            del g1
310            gc.collect()
311            bg_glet_created_running_and_no_longer_ref_in_bg.set()
312            fg_ref_released.wait(3)
313
314            RawGreenlet()   # trigger release
315            bg_should_be_clear.set()
316            ok_to_exit_bg_thread.wait(3)
317            RawGreenlet() # One more time
318
319        t = threading.Thread(target=f)
320        t.start()
321        bg_glet_created_running_and_no_longer_ref_in_bg.wait(10)
322
323        self.assertEqual(seen, [])
324        self.assertEqual(len(someref), 1)
325        del someref[:]
326        if not RUNNING_ON_FREETHREAD_BUILD:
327            # The free-threaded GC is very different. In 3.14rc1,
328            # the free-threaded GC traverses ``g1``, realizes it is
329            # not referenced from anywhere else IT cares about,
330            # calls ``tp_clear`` and then ``green_dealloc``. This causes
331            # the greenlet to lose its reference to the main greenlet and thread
332            # in which it was running, which means we can no longer throw an
333            # exception into it, preventing the rest of this test from working.
334            # Standard 3.14 traverses the object but doesn't ``tp_clear`` or
335            # ``green_dealloc`` it.
336            gc.collect()
337        # g1 is not released immediately because it's from another thread;
338        # switching back to that thread will allocate a greenlet and thus
339        # trigger deletion actions.
340        self.assertEqual(seen, [])
341        fg_ref_released.set()
342        bg_should_be_clear.wait(3)
343        try:
344            self.assertEqual(seen, [greenlet.GreenletExit])
345        finally:
346            ok_to_exit_bg_thread.set()
347            t.join(10)
348            del seen[:]
349            del someref[:]
350
351    def test_frame(self):
352        def f1():
353            f = sys._getframe(0) # pylint:disable=protected-access
354            self.assertEqual(f.f_back, None)
355            greenlet.getcurrent().parent.switch(f)
356            return "meaning of life"
357        g = RawGreenlet(f1)
358        frame = g.switch()
359        self.assertTrue(frame is g.gr_frame)
360        self.assertTrue(g)
361
362        from_g = g.switch()
363        self.assertFalse(g)
364        self.assertEqual(from_g, 'meaning of life')
365        self.assertEqual(g.gr_frame, None)
366
367    def test_thread_bug(self):
368        def runner(x):
369            g = RawGreenlet(lambda: time.sleep(x))
370            g.switch()
371        t1 = threading.Thread(target=runner, args=(0.2,))
372        t2 = threading.Thread(target=runner, args=(0.3,))
373        t1.start()
374        t2.start()
375        t1.join(10)
376        t2.join(10)
377
378    def test_switch_kwargs(self):
379        def run(a, b):
380            self.assertEqual(a, 4)
381            self.assertEqual(b, 2)
382            return 42
383        x = RawGreenlet(run).switch(a=4, b=2)
384        self.assertEqual(x, 42)
385
386    def test_switch_kwargs_to_parent(self):
387        def run(x):
388            greenlet.getcurrent().parent.switch(x=x)
389            greenlet.getcurrent().parent.switch(2, x=3)
390            return x, x ** 2
391        g = RawGreenlet(run)
392        self.assertEqual({'x': 3}, g.switch(3))
393        self.assertEqual(((2,), {'x': 3}), g.switch())
394        self.assertEqual((3, 9), g.switch())
395
396    def test_switch_to_another_thread(self):
397        data = {}
398        created_event = threading.Event()
399        done_event = threading.Event()
400
401        def run():
402            data['g'] = RawGreenlet(lambda: None)
403            created_event.set()
404            done_event.wait(10)
405        thread = threading.Thread(target=run)
406        thread.start()
407        created_event.wait(10)
408        with self.assertRaises(greenlet.error):
409            data['g'].switch()
410        done_event.set()
411        thread.join(10)
412        # XXX: Should handle this automatically
413        data.clear()
414
415    def test_exc_state(self):
416        def f():
417            try:
418                raise ValueError('fun')
419            except: # pylint:disable=bare-except
420                exc_info = sys.exc_info()
421                RawGreenlet(h).switch()
422                self.assertEqual(exc_info, sys.exc_info())
423
424        def h():
425            self.assertEqual(sys.exc_info(), (None, None, None))
426
427        RawGreenlet(f).switch()
428
429    def test_instance_dict(self):
430        def f():
431            greenlet.getcurrent().test = 42
432        def deldict(g):
433            del g.__dict__
434        def setdict(g, value):
435            g.__dict__ = value
436        g = RawGreenlet(f)
437        self.assertEqual(g.__dict__, {})
438        g.switch()
439        self.assertEqual(g.test, 42)
440        self.assertEqual(g.__dict__, {'test': 42})
441        g.__dict__ = g.__dict__
442        self.assertEqual(g.__dict__, {'test': 42})
443        self.assertRaises(TypeError, deldict, g)
444        self.assertRaises(TypeError, setdict, g, 42)
445
446    def test_running_greenlet_has_no_run(self):
447        has_run = []
448        def func():
449            has_run.append(
450                hasattr(greenlet.getcurrent(), 'run')
451            )
452
453        g = RawGreenlet(func)
454        g.switch()
455        self.assertEqual(has_run, [False])
456
457    def test_deepcopy(self):
458        import copy
459        self.assertRaises(TypeError, copy.copy, RawGreenlet())
460        self.assertRaises(TypeError, copy.deepcopy, RawGreenlet())
461
462    def test_parent_restored_on_kill(self):
463        hub = RawGreenlet(lambda: None)
464        main = greenlet.getcurrent()
465        result = []
466        def worker():
467            try:
468                # Wait to be killed by going back to the test.
469                main.switch()
470            except greenlet.GreenletExit:
471                # Resurrect and switch to parent
472                result.append(greenlet.getcurrent().parent)
473                result.append(greenlet.getcurrent())
474                hub.switch()
475        g = RawGreenlet(worker, parent=hub)
476        g.switch()
477        # delete the only reference, thereby raising GreenletExit
478        del g
479        self.assertTrue(result)
480        self.assertIs(result[0], main)
481        self.assertIs(result[1].parent, hub)
482        # Delete them, thereby breaking the cycle between the greenlet
483        # and the frame, which otherwise would never be collectable
484        # XXX: We should be able to automatically fix this.
485        del result[:]
486        hub = None
487        main = None
488
489    def test_parent_return_failure(self):
490        # No run causes AttributeError on switch
491        g1 = RawGreenlet()
492        # Greenlet that implicitly switches to parent
493        g2 = RawGreenlet(lambda: None, parent=g1)
494        # AttributeError should propagate to us, no fatal errors
495        with self.assertRaises(AttributeError):
496            g2.switch()
497
498    def test_throw_exception_not_lost(self):
499        class mygreenlet(RawGreenlet):
500            def __getattribute__(self, name):
501                try:
502                    raise Exception # pylint:disable=broad-exception-raised
503                except: # pylint:disable=bare-except
504                    pass
505                return RawGreenlet.__getattribute__(self, name)
506        g = mygreenlet(lambda: None)
507        self.assertRaises(SomeError, g.throw, SomeError())
508
509    @fails_leakcheck
510    def _do_test_throw_to_dead_thread_doesnt_crash(self, wait_for_cleanup=False):
511        result = []
512        def worker():
513            greenlet.getcurrent().parent.switch()
514
515        def creator():
516            g = RawGreenlet(worker)
517            g.switch()
518            result.append(g)
519            if wait_for_cleanup:
520                # Let this greenlet eventually be cleaned up.
521                g.switch()
522                greenlet.getcurrent()
523        t = threading.Thread(target=creator)
524        t.start()
525        t.join(10)
526        del t
527        # But, depending on the operating system, the thread
528        # deallocator may not actually have run yet! So we can't be
529        # sure about the error message unless we wait.
530        if wait_for_cleanup:
531            self.wait_for_pending_cleanups()
532        with self.assertRaises(greenlet.error) as exc:
533            result[0].throw(SomeError)
534
535        if not wait_for_cleanup:
536            s = str(exc.exception)
537            self.assertTrue(
538                s == "cannot switch to a different thread (which happens to have exited)"
539                or 'Cannot switch' in s
540            )
541        else:
542            self.assertEqual(
543                str(exc.exception),
544                "cannot switch to a different thread (which happens to have exited)",
545            )
546
547        if hasattr(result[0].gr_frame, 'clear'):
548            # The frame is actually executing (it thinks), we can't clear it.
549            with self.assertRaises(RuntimeError):
550                result[0].gr_frame.clear()
551        # Unfortunately, this doesn't actually clear the references, they're in the
552        # fast local array.
553        if not wait_for_cleanup:
554            # f_locals has no clear method in Python 3.13
555            if hasattr(result[0].gr_frame.f_locals, 'clear'):
556                result[0].gr_frame.f_locals.clear()
557        else:
558            self.assertIsNone(result[0].gr_frame)
559
560        del creator
561        worker = None
562        del result[:]
563        # XXX: we ought to be able to automatically fix this.
564        # See issue 252
565        self.expect_greenlet_leak = True # direct us not to wait for it to go away
566
567    @fails_leakcheck
568    def test_throw_to_dead_thread_doesnt_crash(self):
569        self._do_test_throw_to_dead_thread_doesnt_crash()
570
571    def test_throw_to_dead_thread_doesnt_crash_wait(self):
572        self._do_test_throw_to_dead_thread_doesnt_crash(True)
573
574    @fails_leakcheck
575    def test_recursive_startup(self):
576        class convoluted(RawGreenlet):
577            def __init__(self):
578                RawGreenlet.__init__(self)
579                self.count = 0
580            def __getattribute__(self, name):
581                if name == 'run' and self.count == 0:
582                    self.count = 1
583                    self.switch(43)
584                return RawGreenlet.__getattribute__(self, name)
585            def run(self, value):
586                while True:
587                    self.parent.switch(value)
588        g = convoluted()
589        self.assertEqual(g.switch(42), 43)
590        # Exits the running greenlet, otherwise it leaks
591        # XXX: We should be able to automatically fix this
592        #g.throw(greenlet.GreenletExit)
593        #del g
594        self.expect_greenlet_leak = True
595
596    def test_threaded_updatecurrent(self):
597        # released when main thread should execute
598        lock1 = threading.Lock()
599        lock1.acquire()
600        # released when another thread should execute
601        lock2 = threading.Lock()
602        lock2.acquire()
603        class finalized(object):
604            def __del__(self):
605                # happens while in green_updatecurrent() in main greenlet
606                # should be very careful not to accidentally call it again
607                # at the same time we must make sure another thread executes
608                lock2.release()
609                lock1.acquire()
610                # now ts_current belongs to another thread
611        def deallocator():
612            greenlet.getcurrent().parent.switch()
613        def fthread():
614            lock2.acquire()
615            greenlet.getcurrent()
616            del g[0]
617            lock1.release()
618            lock2.acquire()
619            greenlet.getcurrent()
620            lock1.release()
621        main = greenlet.getcurrent()
622        g = [RawGreenlet(deallocator)]
623        g[0].bomb = finalized()
624        g[0].switch()
625        t = threading.Thread(target=fthread)
626        t.start()
627        # let another thread grab ts_current and deallocate g[0]
628        lock2.release()
629        lock1.acquire()
630        # this is the corner stone
631        # getcurrent() will notice that ts_current belongs to another thread
632        # and start the update process, which would notice that g[0] should
633        # be deallocated, and that will execute an object's finalizer. Now,
634        # that object will let another thread run so it can grab ts_current
635        # again, which would likely crash the interpreter if there's no
636        # check for this case at the end of green_updatecurrent(). This test
637        # passes if getcurrent() returns correct result, but it's likely
638        # to randomly crash if it's not anyway.
639        self.assertEqual(greenlet.getcurrent(), main)
640        # wait for another thread to complete, just in case
641        t.join(10)
642
643    def test_dealloc_switch_args_not_lost(self):
644        seen = []
645        def worker():
646            # wait for the value
647            value = greenlet.getcurrent().parent.switch()
648            # delete all references to ourself
649            del worker[0]
650            initiator.parent = greenlet.getcurrent().parent
651            # switch to main with the value, but because
652            # ts_current is the last reference to us we
653            # return here immediately, where we resurrect ourself.
654            try:
655                greenlet.getcurrent().parent.switch(value)
656            finally:
657                seen.append(greenlet.getcurrent())
658        def initiator():
659            return 42 # implicitly falls thru to parent
660
661        worker = [RawGreenlet(worker)]
662
663        worker[0].switch() # prime worker
664        initiator = RawGreenlet(initiator, worker[0])
665        value = initiator.switch()
666        self.assertTrue(seen)
667        self.assertEqual(value, 42)
668
669    def test_tuple_subclass(self):
670        # The point of this test is to see what happens when a custom
671        # tuple subclass is used as an object passed directly to the C
672        # function ``green_switch``; part of ``green_switch`` checks
673        # the ``len()`` of the ``args`` tuple, and that can call back
674        # into Python. Here, when it calls back into Python, we
675        # recursively enter ``green_switch`` again.
676
677        # This test is really only relevant on Python 2. The builtin
678        # `apply` function directly passes the given args tuple object
679        # to the underlying function, whereas the Python 3 version
680        # unpacks and repacks into an actual tuple. This could still
681        # happen using the C API on Python 3 though. We should write a
682        # builtin version of apply() ourself.
683        def _apply(func, a, k):
684            func(*a, **k)
685
686        class mytuple(tuple):
687            def __len__(self):
688                greenlet.getcurrent().switch()
689                return tuple.__len__(self)
690        args = mytuple()
691        kwargs = dict(a=42)
692        def switchapply():
693            _apply(greenlet.getcurrent().parent.switch, args, kwargs)
694        g = RawGreenlet(switchapply)
695        self.assertEqual(g.switch(), kwargs)
696
697    def test_abstract_subclasses(self):
698        AbstractSubclass = ABCMeta(
699            'AbstractSubclass',
700            (RawGreenlet,),
701            {'run': abstractmethod(lambda self: None)})
702
703        class BadSubclass(AbstractSubclass):
704            pass
705
706        class GoodSubclass(AbstractSubclass):
707            def run(self):
708                pass
709
710        GoodSubclass() # should not raise
711        self.assertRaises(TypeError, BadSubclass)
712
713    def test_implicit_parent_with_threads(self):
714        if not gc.isenabled():
715            return # cannot test with disabled gc
716        N = gc.get_threshold()[0]
717        if N < 50:
718            return # cannot test with such a small N
719        def attempt():
720            lock1 = threading.Lock()
721            lock1.acquire()
722            lock2 = threading.Lock()
723            lock2.acquire()
724            recycled = [False]
725            def another_thread():
726                lock1.acquire() # wait for gc
727                greenlet.getcurrent() # update ts_current
728                lock2.release() # release gc
729            t = threading.Thread(target=another_thread)
730            t.start()
731            class gc_callback(object):
732                def __del__(self):
733                    lock1.release()
734                    lock2.acquire()
735                    recycled[0] = True
736            class garbage(object):
737                def __init__(self):
738                    self.cycle = self
739                    self.callback = gc_callback()
740            l = []
741            x = range(N*2)
742            current = greenlet.getcurrent()
743            g = garbage()
744            for _ in x:
745                g = None # lose reference to garbage
746                if recycled[0]:
747                    # gc callback called prematurely
748                    t.join(10)
749                    return False
750                last = RawGreenlet()
751                if recycled[0]:
752                    break # yes! gc called in green_new
753                l.append(last) # increase allocation counter
754            else:
755                # gc callback not called when expected
756                gc.collect()
757                if recycled[0]:
758                    t.join(10)
759                return False
760            self.assertEqual(last.parent, current)
761            for g in l:
762                self.assertEqual(g.parent, current)
763            return True
764        for _ in range(5):
765            if attempt():
766                break
767
768    def test_issue_245_reference_counting_subclass_no_threads(self):
769        # https://github.com/python-greenlet/greenlet/issues/245
770        # Before the fix, this crashed pretty reliably on
771        # Python 3.10, at least on macOS; but much less reliably on other
772        # interpreters (memory layout must have changed).
773        # The threaded test crashed more reliably on more interpreters.
774        from greenlet import getcurrent
775        from greenlet import GreenletExit
776
777        class Greenlet(RawGreenlet):
778            pass
779
780        initial_refs = sys.getrefcount(Greenlet)
781        # This has to be an instance variable because
782        # Python 2 raises a SyntaxError if we delete a local
783        # variable referenced in an inner scope.
784        self.glets = [] # pylint:disable=attribute-defined-outside-init
785
786        def greenlet_main():
787            try:
788                getcurrent().parent.switch()
789            except GreenletExit:
790                self.glets.append(getcurrent())
791
792        # Before the
793        for _ in range(10):
794            Greenlet(greenlet_main).switch()
795
796        del self.glets
797        if RUNNING_ON_FREETHREAD_BUILD:
798            # Free-threaded builds make types immortal, which gives us
799            # weird numbers here, and we actually do APPEAR to end
800            # up with one more reference than we started with, at least on 3.14.
801            # If we change the code in green_dealloc to avoid increffing the type
802            # (which fixed this initial bug), then our leakchecks find other objects
803            # that have leaked, including a tuple, a dict, and a type. So that's not the
804            # right solution. Instead we change the test:
805            # XXX: FIXME: Is there a better way?
806            self.assertGreaterEqual(sys.getrefcount(Greenlet), initial_refs)
807        else:
808            self.assertEqual(sys.getrefcount(Greenlet), initial_refs)
809
810    @unittest.skipIf(
811        PY313 and RUNNING_ON_MANYLINUX,
812        "The manylinux images appear to hang on this test on 3.13rc2"
813        # Or perhaps I just got tired of waiting for the 450s timeout.
814        # Still, it shouldn't take anywhere near that long. Does not reproduce in
815        # Ubuntu images, on macOS or Windows.
816    )
817    def test_issue_245_reference_counting_subclass_threads(self):
818        # https://github.com/python-greenlet/greenlet/issues/245
819        from threading import Thread
820        from threading import Event
821
822        from greenlet import getcurrent
823
824        class MyGreenlet(RawGreenlet):
825            pass
826
827        glets = []
828        ref_cleared = Event()
829
830        def greenlet_main():
831            getcurrent().parent.switch()
832
833        def thread_main(greenlet_running_event):
834            mine = MyGreenlet(greenlet_main)
835            glets.append(mine)
836            # The greenlets being deleted must be active
837            mine.switch()
838            # Don't keep any reference to it in this thread
839            del mine
840            # Let main know we published our greenlet.
841            greenlet_running_event.set()
842            # Wait for main to let us know the references are
843            # gone and the greenlet objects no longer reachable
844            ref_cleared.wait(10)
845            # The creating thread must call getcurrent() (or a few other
846            # greenlet APIs) because that's when the thread-local list of dead
847            # greenlets gets cleared.
848            getcurrent()
849
850        # We start with 3 references to the subclass:
851        # - This module
852        # - Its __mro__
853        # - The __subclassess__ attribute of greenlet
854        # - (If we call gc.get_referents(), we find four entries, including
855        #   some other tuple ``(greenlet)`` that I'm not sure about but must be part
856        #   of the machinery.)
857        #
858        # On Python 3.10 it's often enough to just run 3 threads; on Python 2.7,
859        # more threads are needed, and the results are still
860        # non-deterministic. Presumably the memory layouts are different
861        initial_refs = sys.getrefcount(MyGreenlet)
862        thread_ready_events = []
863        thread_count = initial_refs + 45
864        if RUNNING_ON_FREETHREAD_BUILD:
865            # types are immortal, so this is a HUGE number most likely,
866            # and we can't create that many threads.
867            thread_count = 50
868        for _ in range(thread_count):
869            event = Event()
870            thread = Thread(target=thread_main, args=(event,))
871            thread_ready_events.append(event)
872            thread.start()
873
874
875        for done_event in thread_ready_events:
876            done_event.wait(10)
877
878
879        del glets[:]
880        ref_cleared.set()
881        # Let any other thread run; it will crash the interpreter
882        # if not fixed (or silently corrupt memory and we possibly crash
883        # later).
884        self.wait_for_pending_cleanups()
885        self.assertEqual(sys.getrefcount(MyGreenlet), initial_refs)
886
887    def test_falling_off_end_switches_to_unstarted_parent_raises_error(self):
888        def no_args():
889            return 13
890
891        parent_never_started = RawGreenlet(no_args)
892
893        def leaf():
894            return 42
895
896        child = RawGreenlet(leaf, parent_never_started)
897
898        # Because the run function takes to arguments
899        with self.assertRaises(TypeError):
900            child.switch()
901
902    def test_falling_off_end_switches_to_unstarted_parent_works(self):
903        def one_arg(x):
904            return (x, 24)
905
906        parent_never_started = RawGreenlet(one_arg)
907
908        def leaf():
909            return 42
910
911        child = RawGreenlet(leaf, parent_never_started)
912
913        result = child.switch()
914        self.assertEqual(result, (42, 24))
915
916    def test_switch_to_dead_greenlet_with_unstarted_perverse_parent(self):
917        class Parent(RawGreenlet):
918            def __getattribute__(self, name):
919                if name == 'run':
920                    raise SomeError
921
922
923        parent_never_started = Parent()
924        seen = []
925        child = RawGreenlet(lambda: seen.append(42), parent_never_started)
926        # Because we automatically start the parent when the child is
927        # finished
928        with self.assertRaises(SomeError):
929            child.switch()
930
931        self.assertEqual(seen, [42])
932
933        with self.assertRaises(SomeError):
934            child.switch()
935        self.assertEqual(seen, [42])
936
937    def test_switch_to_dead_greenlet_reparent(self):
938        seen = []
939        parent_never_started = RawGreenlet(lambda: seen.append(24))
940        child = RawGreenlet(lambda: seen.append(42))
941
942        child.switch()
943        self.assertEqual(seen, [42])
944
945        child.parent = parent_never_started
946        # This actually is the same as switching to the parent.
947        result = child.switch()
948        self.assertIsNone(result)
949        self.assertEqual(seen, [42, 24])
950
951    def test_can_access_f_back_of_suspended_greenlet(self):
952        # This tests our frame rewriting to work around Python 3.12+ having
953        # some interpreter frames on the C stack. It will crash in the absence
954        # of that logic.
955        main = greenlet.getcurrent()
956
957        def outer():
958            inner()
959
960        def inner():
961            main.switch(sys._getframe(0))
962
963        hub = RawGreenlet(outer)
964        # start it
965        hub.switch()
966
967        # start another greenlet to make sure we aren't relying on
968        # anything in `hub` still being on the C stack
969        unrelated = RawGreenlet(lambda: None)
970        unrelated.switch()
971
972        # now it is suspended
973        self.assertIsNotNone(hub.gr_frame)
974        self.assertEqual(hub.gr_frame.f_code.co_name, "inner")
975        self.assertIsNotNone(hub.gr_frame.f_back)
976        self.assertEqual(hub.gr_frame.f_back.f_code.co_name, "outer")
977        # The next line is what would crash
978        self.assertIsNone(hub.gr_frame.f_back.f_back)
979
980    def test_get_stack_with_nested_c_calls(self):
981        from functools import partial
982        from . import _test_extension_cpp
983
984        def recurse(v):
985            if v > 0:
986                return v * _test_extension_cpp.test_call(partial(recurse, v - 1))
987            return greenlet.getcurrent().parent.switch()
988
989        gr = RawGreenlet(recurse)
990        gr.switch(5)
991        frame = gr.gr_frame
992        for i in range(5):
993            self.assertEqual(frame.f_locals["v"], i)
994            frame = frame.f_back
995        self.assertEqual(frame.f_locals["v"], 5)
996        self.assertIsNone(frame.f_back)
997        self.assertEqual(gr.switch(10), 1200)  # 1200 = 5! * 10
998
999    def test_frames_always_exposed(self):
1000        # On Python 3.12 this will crash if we don't set the
1001        # gr_frames_always_exposed attribute. More background:
1002        # https://github.com/python-greenlet/greenlet/issues/388
1003        main = greenlet.getcurrent()
1004
1005        def outer():
1006            inner(sys._getframe(0))
1007
1008        def inner(frame):
1009            main.switch(frame)
1010
1011        gr = RawGreenlet(outer)
1012        frame = gr.switch()
1013
1014        # Do something else to clobber the part of the C stack used by `gr`,
1015        # so we can't skate by on "it just happened to still be there"
1016        unrelated = RawGreenlet(lambda: None)
1017        unrelated.switch()
1018
1019        self.assertEqual(frame.f_code.co_name, "outer")
1020        # The next line crashes on 3.12 if we haven't exposed the frames.
1021        self.assertIsNone(frame.f_back)
1022
1023
1024class TestGreenletSetParentErrors(TestCase):
1025    def test_threaded_reparent(self):
1026        data = {}
1027        created_event = threading.Event()
1028        done_event = threading.Event()
1029
1030        def run():
1031            data['g'] = RawGreenlet(lambda: None)
1032            created_event.set()
1033            done_event.wait(10)
1034
1035        def blank():
1036            greenlet.getcurrent().parent.switch()
1037
1038        thread = threading.Thread(target=run)
1039        thread.start()
1040        created_event.wait(10)
1041        g = RawGreenlet(blank)
1042        g.switch()
1043        with self.assertRaises(ValueError) as exc:
1044            g.parent = data['g']
1045        done_event.set()
1046        thread.join(10)
1047
1048        self.assertEqual(str(exc.exception), "parent cannot be on a different thread")
1049
1050    def test_unexpected_reparenting(self):
1051        another = []
1052        def worker():
1053            g = RawGreenlet(lambda: None)
1054            another.append(g)
1055            g.switch()
1056        t = threading.Thread(target=worker)
1057        t.start()
1058        t.join(10)
1059        # The first time we switch (running g_initialstub(), which is
1060        # when we look up the run attribute) we attempt to change the
1061        # parent to one from another thread (which also happens to be
1062        # dead). ``g_initialstub()`` should detect this and raise a
1063        # greenlet error.
1064        #
1065        # EXCEPT: With the fix for #252, this is actually detected
1066        # sooner, when setting the parent itself. Prior to that fix,
1067        # the main greenlet from the background thread kept a valid
1068        # value for ``run_info``, and appeared to be a valid parent
1069        # until we actually started the greenlet. But now that it's
1070        # cleared, this test is catching whether ``green_setparent``
1071        # can detect the dead thread.
1072        #
1073        # Further refactoring once again changes this back to a greenlet.error
1074        #
1075        # We need to wait for the cleanup to happen, but we're
1076        # deliberately leaking a main greenlet here.
1077        self.wait_for_pending_cleanups(initial_main_greenlets=self.main_greenlets_before_test + 1)
1078
1079        class convoluted(RawGreenlet):
1080            def __getattribute__(self, name):
1081                if name == 'run':
1082                    self.parent = another[0] # pylint:disable=attribute-defined-outside-init
1083                return RawGreenlet.__getattribute__(self, name)
1084        g = convoluted(lambda: None)
1085        with self.assertRaises(greenlet.error) as exc:
1086            g.switch()
1087        self.assertEqual(str(exc.exception),
1088                         "cannot switch to a different thread (which happens to have exited)")
1089        del another[:]
1090
1091    def test_unexpected_reparenting_thread_running(self):
1092        # Like ``test_unexpected_reparenting``, except the background thread is
1093        # actually still alive.
1094        another = []
1095        switched_to_greenlet = threading.Event()
1096        keep_main_alive = threading.Event()
1097        def worker():
1098            g = RawGreenlet(lambda: None)
1099            another.append(g)
1100            g.switch()
1101            switched_to_greenlet.set()
1102            keep_main_alive.wait(10)
1103        class convoluted(RawGreenlet):
1104            def __getattribute__(self, name):
1105                if name == 'run':
1106                    self.parent = another[0] # pylint:disable=attribute-defined-outside-init
1107                return RawGreenlet.__getattribute__(self, name)
1108
1109        t = threading.Thread(target=worker)
1110        t.start()
1111
1112        switched_to_greenlet.wait(10)
1113        try:
1114            g = convoluted(lambda: None)
1115
1116            with self.assertRaises(greenlet.error) as exc:
1117                g.switch()
1118            self.assertIn("Cannot switch to a different thread", str(exc.exception))
1119            self.assertIn("Expected", str(exc.exception))
1120            self.assertIn("Current", str(exc.exception))
1121        finally:
1122            keep_main_alive.set()
1123            t.join(10)
1124            # XXX: Should handle this automatically.
1125            del another[:]
1126
1127    def test_cannot_delete_parent(self):
1128        worker = RawGreenlet(lambda: None)
1129        self.assertIs(worker.parent, greenlet.getcurrent())
1130
1131        with self.assertRaises(AttributeError) as exc:
1132            del worker.parent
1133        self.assertEqual(str(exc.exception), "can't delete attribute")
1134
1135    def test_cannot_delete_parent_of_main(self):
1136        with self.assertRaises(AttributeError) as exc:
1137            del greenlet.getcurrent().parent
1138        self.assertEqual(str(exc.exception), "can't delete attribute")
1139
1140
1141    def test_main_greenlet_parent_is_none(self):
1142        # assuming we're in a main greenlet here.
1143        self.assertIsNone(greenlet.getcurrent().parent)
1144
1145    def test_set_parent_wrong_types(self):
1146        def bg():
1147            # Go back to main.
1148            greenlet.getcurrent().parent.switch()
1149
1150        def check(glet):
1151            for p in None, 1, self, "42":
1152                with self.assertRaises(TypeError) as exc:
1153                    glet.parent = p
1154
1155                self.assertEqual(
1156                    str(exc.exception),
1157                    "GreenletChecker: Expected any type of greenlet, not " + type(p).__name__)
1158
1159        # First, not running
1160        g = RawGreenlet(bg)
1161        self.assertFalse(g)
1162        check(g)
1163
1164        # Then when running.
1165        g.switch()
1166        self.assertTrue(g)
1167        check(g)
1168
1169        # Let it finish
1170        g.switch()
1171
1172
1173    def test_trivial_cycle(self):
1174        glet = RawGreenlet(lambda: None)
1175        with self.assertRaises(ValueError) as exc:
1176            glet.parent = glet
1177        self.assertEqual(str(exc.exception), "cyclic parent chain")
1178
1179    def test_trivial_cycle_main(self):
1180        # This used to produce a ValueError, but we catch it earlier than that now.
1181        with self.assertRaises(AttributeError) as exc:
1182            greenlet.getcurrent().parent = greenlet.getcurrent()
1183        self.assertEqual(str(exc.exception), "cannot set the parent of a main greenlet")
1184
1185    def test_deeper_cycle(self):
1186        g1 = RawGreenlet(lambda: None)
1187        g2 = RawGreenlet(lambda: None)
1188        g3 = RawGreenlet(lambda: None)
1189
1190        g1.parent = g2
1191        g2.parent = g3
1192        with self.assertRaises(ValueError) as exc:
1193            g3.parent = g1
1194        self.assertEqual(str(exc.exception), "cyclic parent chain")
1195
1196
1197class TestRepr(TestCase):
1198
1199    if not hasattr(TestCase, 'assertEndsWith'): # Added in 3.14
1200        def assertEndsWith(self, s, suffix, msg=None):

Showing the first 1,200 of 1427 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai