codekingpro/portable-devtools
114k
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):
