You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

729 lines
23 KiB

  1. import gc
  2. import sys
  3. import time
  4. import threading
  5. import unittest
  6. from abc import ABCMeta, abstractmethod
  7. from greenlet import greenlet
  8. # We manually manage locks in many tests
  9. # pylint:disable=consider-using-with
  10. class SomeError(Exception):
  11. pass
  12. def fmain(seen):
  13. try:
  14. greenlet.getcurrent().parent.switch()
  15. except:
  16. seen.append(sys.exc_info()[0])
  17. raise
  18. raise SomeError
  19. def send_exception(g, exc):
  20. # note: send_exception(g, exc) can be now done with g.throw(exc).
  21. # the purpose of this test is to explicitely check the propagation rules.
  22. def crasher(exc):
  23. raise exc
  24. g1 = greenlet(crasher, parent=g)
  25. g1.switch(exc)
  26. class TestGreenlet(unittest.TestCase):
  27. def test_simple(self):
  28. lst = []
  29. def f():
  30. lst.append(1)
  31. greenlet.getcurrent().parent.switch()
  32. lst.append(3)
  33. g = greenlet(f)
  34. lst.append(0)
  35. g.switch()
  36. lst.append(2)
  37. g.switch()
  38. lst.append(4)
  39. self.assertEqual(lst, list(range(5)))
  40. def test_parent_equals_None(self):
  41. g = greenlet(parent=None)
  42. self.assertIsNotNone(g)
  43. self.assertIs(g.parent, greenlet.getcurrent())
  44. def test_run_equals_None(self):
  45. g = greenlet(run=None)
  46. self.assertIsNotNone(g)
  47. self.assertIsNone(g.run)
  48. def test_two_children(self):
  49. lst = []
  50. def f():
  51. lst.append(1)
  52. greenlet.getcurrent().parent.switch()
  53. lst.extend([1, 1])
  54. g = greenlet(f)
  55. h = greenlet(f)
  56. g.switch()
  57. self.assertEqual(len(lst), 1)
  58. h.switch()
  59. self.assertEqual(len(lst), 2)
  60. h.switch()
  61. self.assertEqual(len(lst), 4)
  62. self.assertEqual(h.dead, True)
  63. g.switch()
  64. self.assertEqual(len(lst), 6)
  65. self.assertEqual(g.dead, True)
  66. def test_two_recursive_children(self):
  67. lst = []
  68. def f():
  69. lst.append(1)
  70. greenlet.getcurrent().parent.switch()
  71. def g():
  72. lst.append(1)
  73. g = greenlet(f)
  74. g.switch()
  75. lst.append(1)
  76. g = greenlet(g)
  77. g.switch()
  78. self.assertEqual(len(lst), 3)
  79. self.assertEqual(sys.getrefcount(g), 2)
  80. def test_threads(self):
  81. success = []
  82. def f():
  83. self.test_simple()
  84. success.append(True)
  85. ths = [threading.Thread(target=f) for i in range(10)]
  86. for th in ths:
  87. th.start()
  88. for th in ths:
  89. th.join()
  90. self.assertEqual(len(success), len(ths))
  91. def test_exception(self):
  92. seen = []
  93. g1 = greenlet(fmain)
  94. g2 = greenlet(fmain)
  95. g1.switch(seen)
  96. g2.switch(seen)
  97. g2.parent = g1
  98. self.assertEqual(seen, [])
  99. self.assertRaises(SomeError, g2.switch)
  100. self.assertEqual(seen, [SomeError])
  101. g2.switch()
  102. self.assertEqual(seen, [SomeError])
  103. def test_send_exception(self):
  104. seen = []
  105. g1 = greenlet(fmain)
  106. g1.switch(seen)
  107. self.assertRaises(KeyError, send_exception, g1, KeyError)
  108. self.assertEqual(seen, [KeyError])
  109. def test_dealloc(self):
  110. seen = []
  111. g1 = greenlet(fmain)
  112. g2 = greenlet(fmain)
  113. g1.switch(seen)
  114. g2.switch(seen)
  115. self.assertEqual(seen, [])
  116. del g1
  117. gc.collect()
  118. self.assertEqual(seen, [greenlet.GreenletExit])
  119. del g2
  120. gc.collect()
  121. self.assertEqual(seen, [greenlet.GreenletExit, greenlet.GreenletExit])
  122. def test_dealloc_other_thread(self):
  123. seen = []
  124. someref = []
  125. lock = threading.Lock()
  126. lock.acquire()
  127. lock2 = threading.Lock()
  128. lock2.acquire()
  129. def f():
  130. g1 = greenlet(fmain)
  131. g1.switch(seen)
  132. someref.append(g1)
  133. del g1
  134. gc.collect()
  135. lock.release()
  136. lock2.acquire()
  137. greenlet() # trigger release
  138. lock.release()
  139. lock2.acquire()
  140. t = threading.Thread(target=f)
  141. t.start()
  142. lock.acquire()
  143. self.assertEqual(seen, [])
  144. self.assertEqual(len(someref), 1)
  145. del someref[:]
  146. gc.collect()
  147. # g1 is not released immediately because it's from another thread
  148. self.assertEqual(seen, [])
  149. lock2.release()
  150. lock.acquire()
  151. self.assertEqual(seen, [greenlet.GreenletExit])
  152. lock2.release()
  153. t.join()
  154. def test_frame(self):
  155. def f1():
  156. f = sys._getframe(0) # pylint:disable=protected-access
  157. self.assertEqual(f.f_back, None)
  158. greenlet.getcurrent().parent.switch(f)
  159. return "meaning of life"
  160. g = greenlet(f1)
  161. frame = g.switch()
  162. self.assertTrue(frame is g.gr_frame)
  163. self.assertTrue(g)
  164. from_g = g.switch()
  165. self.assertFalse(g)
  166. self.assertEqual(from_g, 'meaning of life')
  167. self.assertEqual(g.gr_frame, None)
  168. def test_thread_bug(self):
  169. def runner(x):
  170. g = greenlet(lambda: time.sleep(x))
  171. g.switch()
  172. t1 = threading.Thread(target=runner, args=(0.2,))
  173. t2 = threading.Thread(target=runner, args=(0.3,))
  174. t1.start()
  175. t2.start()
  176. t1.join()
  177. t2.join()
  178. def test_switch_kwargs(self):
  179. def run(a, b):
  180. self.assertEqual(a, 4)
  181. self.assertEqual(b, 2)
  182. return 42
  183. x = greenlet(run).switch(a=4, b=2)
  184. self.assertEqual(x, 42)
  185. def test_switch_kwargs_to_parent(self):
  186. def run(x):
  187. greenlet.getcurrent().parent.switch(x=x)
  188. greenlet.getcurrent().parent.switch(2, x=3)
  189. return x, x ** 2
  190. g = greenlet(run)
  191. self.assertEqual({'x': 3}, g.switch(3))
  192. self.assertEqual(((2,), {'x': 3}), g.switch())
  193. self.assertEqual((3, 9), g.switch())
  194. def test_switch_to_another_thread(self):
  195. data = {}
  196. error = None
  197. created_event = threading.Event()
  198. done_event = threading.Event()
  199. def run():
  200. data['g'] = greenlet(lambda: None)
  201. created_event.set()
  202. done_event.wait()
  203. thread = threading.Thread(target=run)
  204. thread.start()
  205. created_event.wait()
  206. try:
  207. data['g'].switch()
  208. except greenlet.error:
  209. error = sys.exc_info()[1]
  210. self.assertIsNotNone(error, "greenlet.error was not raised!")
  211. done_event.set()
  212. thread.join()
  213. def test_exc_state(self):
  214. def f():
  215. try:
  216. raise ValueError('fun')
  217. except: # pylint:disable=bare-except
  218. exc_info = sys.exc_info()
  219. greenlet(h).switch()
  220. self.assertEqual(exc_info, sys.exc_info())
  221. def h():
  222. self.assertEqual(sys.exc_info(), (None, None, None))
  223. greenlet(f).switch()
  224. def test_instance_dict(self):
  225. def f():
  226. greenlet.getcurrent().test = 42
  227. def deldict(g):
  228. del g.__dict__
  229. def setdict(g, value):
  230. g.__dict__ = value
  231. g = greenlet(f)
  232. self.assertEqual(g.__dict__, {})
  233. g.switch()
  234. self.assertEqual(g.test, 42)
  235. self.assertEqual(g.__dict__, {'test': 42})
  236. g.__dict__ = g.__dict__
  237. self.assertEqual(g.__dict__, {'test': 42})
  238. self.assertRaises(TypeError, deldict, g)
  239. self.assertRaises(TypeError, setdict, g, 42)
  240. def test_threaded_reparent(self):
  241. data = {}
  242. created_event = threading.Event()
  243. done_event = threading.Event()
  244. def run():
  245. data['g'] = greenlet(lambda: None)
  246. created_event.set()
  247. done_event.wait()
  248. def blank():
  249. greenlet.getcurrent().parent.switch()
  250. def setparent(g, value):
  251. g.parent = value
  252. thread = threading.Thread(target=run)
  253. thread.start()
  254. created_event.wait()
  255. g = greenlet(blank)
  256. g.switch()
  257. self.assertRaises(ValueError, setparent, g, data['g'])
  258. done_event.set()
  259. thread.join()
  260. def test_deepcopy(self):
  261. import copy
  262. self.assertRaises(TypeError, copy.copy, greenlet())
  263. self.assertRaises(TypeError, copy.deepcopy, greenlet())
  264. def test_parent_restored_on_kill(self):
  265. hub = greenlet(lambda: None)
  266. main = greenlet.getcurrent()
  267. result = []
  268. def worker():
  269. try:
  270. # Wait to be killed
  271. main.switch()
  272. except greenlet.GreenletExit:
  273. # Resurrect and switch to parent
  274. result.append(greenlet.getcurrent().parent)
  275. result.append(greenlet.getcurrent())
  276. hub.switch()
  277. g = greenlet(worker, parent=hub)
  278. g.switch()
  279. del g
  280. self.assertTrue(result)
  281. self.assertEqual(result[0], main)
  282. self.assertEqual(result[1].parent, hub)
  283. def test_parent_return_failure(self):
  284. # No run causes AttributeError on switch
  285. g1 = greenlet()
  286. # Greenlet that implicitly switches to parent
  287. g2 = greenlet(lambda: None, parent=g1)
  288. # AttributeError should propagate to us, no fatal errors
  289. self.assertRaises(AttributeError, g2.switch)
  290. def test_throw_exception_not_lost(self):
  291. class mygreenlet(greenlet):
  292. def __getattribute__(self, name):
  293. try:
  294. raise Exception()
  295. except: # pylint:disable=bare-except
  296. pass
  297. return greenlet.__getattribute__(self, name)
  298. g = mygreenlet(lambda: None)
  299. self.assertRaises(SomeError, g.throw, SomeError())
  300. def test_throw_doesnt_crash(self):
  301. result = []
  302. def worker():
  303. greenlet.getcurrent().parent.switch()
  304. def creator():
  305. g = greenlet(worker)
  306. g.switch()
  307. result.append(g)
  308. t = threading.Thread(target=creator)
  309. t.start()
  310. t.join()
  311. self.assertRaises(greenlet.error, result[0].throw, SomeError())
  312. def test_recursive_startup(self):
  313. class convoluted(greenlet):
  314. def __init__(self):
  315. greenlet.__init__(self)
  316. self.count = 0
  317. def __getattribute__(self, name):
  318. if name == 'run' and self.count == 0:
  319. self.count = 1
  320. self.switch(43)
  321. return greenlet.__getattribute__(self, name)
  322. def run(self, value):
  323. while True:
  324. self.parent.switch(value)
  325. g = convoluted()
  326. self.assertEqual(g.switch(42), 43)
  327. def test_unexpected_reparenting(self):
  328. another = []
  329. def worker():
  330. g = greenlet(lambda: None)
  331. another.append(g)
  332. g.switch()
  333. t = threading.Thread(target=worker)
  334. t.start()
  335. t.join()
  336. class convoluted(greenlet):
  337. def __getattribute__(self, name):
  338. if name == 'run':
  339. self.parent = another[0] # pylint:disable=attribute-defined-outside-init
  340. return greenlet.__getattribute__(self, name)
  341. g = convoluted(lambda: None)
  342. self.assertRaises(greenlet.error, g.switch)
  343. def test_threaded_updatecurrent(self):
  344. # released when main thread should execute
  345. lock1 = threading.Lock()
  346. lock1.acquire()
  347. # released when another thread should execute
  348. lock2 = threading.Lock()
  349. lock2.acquire()
  350. class finalized(object):
  351. def __del__(self):
  352. # happens while in green_updatecurrent() in main greenlet
  353. # should be very careful not to accidentally call it again
  354. # at the same time we must make sure another thread executes
  355. lock2.release()
  356. lock1.acquire()
  357. # now ts_current belongs to another thread
  358. def deallocator():
  359. greenlet.getcurrent().parent.switch()
  360. def fthread():
  361. lock2.acquire()
  362. greenlet.getcurrent()
  363. del g[0]
  364. lock1.release()
  365. lock2.acquire()
  366. greenlet.getcurrent()
  367. lock1.release()
  368. main = greenlet.getcurrent()
  369. g = [greenlet(deallocator)]
  370. g[0].bomb = finalized()
  371. g[0].switch()
  372. t = threading.Thread(target=fthread)
  373. t.start()
  374. # let another thread grab ts_current and deallocate g[0]
  375. lock2.release()
  376. lock1.acquire()
  377. # this is the corner stone
  378. # getcurrent() will notice that ts_current belongs to another thread
  379. # and start the update process, which would notice that g[0] should
  380. # be deallocated, and that will execute an object's finalizer. Now,
  381. # that object will let another thread run so it can grab ts_current
  382. # again, which would likely crash the interpreter if there's no
  383. # check for this case at the end of green_updatecurrent(). This test
  384. # passes if getcurrent() returns correct result, but it's likely
  385. # to randomly crash if it's not anyway.
  386. self.assertEqual(greenlet.getcurrent(), main)
  387. # wait for another thread to complete, just in case
  388. t.join()
  389. def test_dealloc_switch_args_not_lost(self):
  390. seen = []
  391. def worker():
  392. # wait for the value
  393. value = greenlet.getcurrent().parent.switch()
  394. # delete all references to ourself
  395. del worker[0]
  396. initiator.parent = greenlet.getcurrent().parent
  397. # switch to main with the value, but because
  398. # ts_current is the last reference to us we
  399. # return immediately
  400. try:
  401. greenlet.getcurrent().parent.switch(value)
  402. finally:
  403. seen.append(greenlet.getcurrent())
  404. def initiator():
  405. return 42 # implicitly falls thru to parent
  406. worker = [greenlet(worker)]
  407. worker[0].switch() # prime worker
  408. initiator = greenlet(initiator, worker[0])
  409. value = initiator.switch()
  410. self.assertTrue(seen)
  411. self.assertEqual(value, 42)
  412. def test_tuple_subclass(self):
  413. if sys.version_info[0] > 2:
  414. # There's no apply in Python 3.x
  415. def _apply(func, a, k):
  416. func(*a, **k)
  417. else:
  418. _apply = apply # pylint:disable=undefined-variable
  419. class mytuple(tuple):
  420. def __len__(self):
  421. greenlet.getcurrent().switch()
  422. return tuple.__len__(self)
  423. args = mytuple()
  424. kwargs = dict(a=42)
  425. def switchapply():
  426. _apply(greenlet.getcurrent().parent.switch, args, kwargs)
  427. g = greenlet(switchapply)
  428. self.assertEqual(g.switch(), kwargs)
  429. def test_abstract_subclasses(self):
  430. AbstractSubclass = ABCMeta(
  431. 'AbstractSubclass',
  432. (greenlet,),
  433. {'run': abstractmethod(lambda self: None)})
  434. class BadSubclass(AbstractSubclass):
  435. pass
  436. class GoodSubclass(AbstractSubclass):
  437. def run(self):
  438. pass
  439. GoodSubclass() # should not raise
  440. self.assertRaises(TypeError, BadSubclass)
  441. def test_implicit_parent_with_threads(self):
  442. if not gc.isenabled():
  443. return # cannot test with disabled gc
  444. N = gc.get_threshold()[0]
  445. if N < 50:
  446. return # cannot test with such a small N
  447. def attempt():
  448. lock1 = threading.Lock()
  449. lock1.acquire()
  450. lock2 = threading.Lock()
  451. lock2.acquire()
  452. recycled = [False]
  453. def another_thread():
  454. lock1.acquire() # wait for gc
  455. greenlet.getcurrent() # update ts_current
  456. lock2.release() # release gc
  457. t = threading.Thread(target=another_thread)
  458. t.start()
  459. class gc_callback(object):
  460. def __del__(self):
  461. lock1.release()
  462. lock2.acquire()
  463. recycled[0] = True
  464. class garbage(object):
  465. def __init__(self):
  466. self.cycle = self
  467. self.callback = gc_callback()
  468. l = []
  469. x = range(N*2)
  470. current = greenlet.getcurrent()
  471. g = garbage()
  472. for _ in x:
  473. g = None # lose reference to garbage
  474. if recycled[0]:
  475. # gc callback called prematurely
  476. t.join()
  477. return False
  478. last = greenlet()
  479. if recycled[0]:
  480. break # yes! gc called in green_new
  481. l.append(last) # increase allocation counter
  482. else:
  483. # gc callback not called when expected
  484. gc.collect()
  485. if recycled[0]:
  486. t.join()
  487. return False
  488. self.assertEqual(last.parent, current)
  489. for g in l:
  490. self.assertEqual(g.parent, current)
  491. return True
  492. for _ in range(5):
  493. if attempt():
  494. break
  495. def test_issue_245_reference_counting_subclass_no_threads(self):
  496. # https://github.com/python-greenlet/greenlet/issues/245
  497. # Before the fix, this crashed pretty reliably on
  498. # Python 3.10, at least on macOS; but much less reliably on other
  499. # interpreters (memory layout must have changed).
  500. # The threaded test crashed more reliably on more interpreters.
  501. from greenlet import getcurrent
  502. from greenlet import GreenletExit
  503. class Greenlet(greenlet):
  504. pass
  505. initial_refs = sys.getrefcount(Greenlet)
  506. # This has to be an instance variable because
  507. # Python 2 raises a SyntaxError if we delete a local
  508. # variable referenced in an inner scope.
  509. self.glets = [] # pylint:disable=attribute-defined-outside-init
  510. def greenlet_main():
  511. try:
  512. getcurrent().parent.switch()
  513. except GreenletExit:
  514. self.glets.append(getcurrent())
  515. # Before the
  516. for _ in range(10):
  517. Greenlet(greenlet_main).switch()
  518. del self.glets
  519. self.assertEqual(sys.getrefcount(Greenlet), initial_refs)
  520. def test_issue_245_reference_counting_subclass_threads(self):
  521. # https://github.com/python-greenlet/greenlet/issues/245
  522. from threading import Thread
  523. from threading import Event
  524. from greenlet import getcurrent
  525. class MyGreenlet(greenlet):
  526. pass
  527. glets = []
  528. ref_cleared = Event()
  529. def greenlet_main():
  530. getcurrent().parent.switch()
  531. def thread_main(greenlet_running_event):
  532. mine = MyGreenlet(greenlet_main)
  533. glets.append(mine)
  534. # The greenlets being deleted must be active
  535. mine.switch()
  536. # Don't keep any reference to it in this thread
  537. del mine
  538. # Let main know we published our greenlet.
  539. greenlet_running_event.set()
  540. # Wait for main to let us know the references are
  541. # gone and the greenlet objects no longer reachable
  542. ref_cleared.wait()
  543. # The creating thread must call getcurrent() (or a few other
  544. # greenlet APIs) because that's when the thread-local list of dead
  545. # greenlets gets cleared.
  546. getcurrent()
  547. # We start with 3 references to the subclass:
  548. # - This module
  549. # - Its __mro__
  550. # - The __subclassess__ attribute of greenlet
  551. # - (If we call gc.get_referents(), we find four entries, including
  552. # some other tuple ``(greenlet)`` that I'm not sure about but must be part
  553. # of the machinery.)
  554. #
  555. # On Python 3.10 it's often enough to just run 3 threads; on Python 2.7,
  556. # more threads are needed, and the results are still
  557. # non-deterministic. Presumably the memory layouts are different
  558. initial_refs = sys.getrefcount(MyGreenlet)
  559. thread_ready_events = []
  560. for _ in range(
  561. initial_refs + 45
  562. ):
  563. event = Event()
  564. thread = Thread(target=thread_main, args=(event,))
  565. thread_ready_events.append(event)
  566. thread.start()
  567. for done_event in thread_ready_events:
  568. done_event.wait()
  569. del glets[:]
  570. ref_cleared.set()
  571. # Let any other thread run; it will crash the interpreter
  572. # if not fixed (or silently corrupt memory and we possibly crash
  573. # later).
  574. time.sleep(1)
  575. self.assertEqual(sys.getrefcount(MyGreenlet), initial_refs)
  576. class TestRepr(unittest.TestCase):
  577. def assertEndsWith(self, got, suffix):
  578. self.assertTrue(got.endswith(suffix), (got, suffix))
  579. def test_main_while_running(self):
  580. r = repr(greenlet.getcurrent())
  581. self.assertEndsWith(r, " current active started main>")
  582. def test_main_in_background(self):
  583. main = greenlet.getcurrent()
  584. def run():
  585. return repr(main)
  586. g = greenlet(run)
  587. r = g.switch()
  588. self.assertEndsWith(r, ' suspended active started main>')
  589. def test_initial(self):
  590. r = repr(greenlet())
  591. self.assertEndsWith(r, ' pending>')
  592. def test_main_from_other_thread(self):
  593. main = greenlet.getcurrent()
  594. class T(threading.Thread):
  595. original_main = thread_main = None
  596. main_glet = None
  597. def run(self):
  598. self.original_main = repr(main)
  599. self.main_glet = greenlet.getcurrent()
  600. self.thread_main = repr(self.main_glet)
  601. t = T()
  602. t.start()
  603. t.join(10)
  604. self.assertEndsWith(t.original_main, ' suspended active started main>')
  605. self.assertEndsWith(t.thread_main, ' current active started main>')
  606. r = repr(t.main_glet)
  607. # main greenlets, even from dead threads, never really appear dead
  608. # TODO: Can we find a better way to differentiate that?
  609. assert not t.main_glet.dead
  610. self.assertEndsWith(r, ' suspended active started main>')
  611. def test_dead(self):
  612. g = greenlet(lambda: None)
  613. g.switch()
  614. self.assertEndsWith(repr(g), ' dead>')
  615. self.assertNotIn('suspended', repr(g))
  616. self.assertNotIn('started', repr(g))
  617. self.assertNotIn('active', repr(g))
  618. def test_formatting_produces_native_str(self):
  619. # https://github.com/python-greenlet/greenlet/issues/218
  620. # %s formatting on Python 2 was producing unicode, not str.
  621. g_dead = greenlet(lambda: None)
  622. g_not_started = greenlet(lambda: None)
  623. g_cur = greenlet.getcurrent()
  624. for g in g_dead, g_not_started, g_cur:
  625. self.assertIsInstance(
  626. '%s' % (g,),
  627. str
  628. )
  629. self.assertIsInstance(
  630. '%r' % (g,),
  631. str,
  632. )
  633. if __name__ == '__main__':
  634. unittest.main()