test_contextvars.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. from __future__ import print_function
  2. import gc
  3. import sys
  4. from functools import partial
  5. from unittest import skipUnless
  6. from unittest import skipIf
  7. from greenlet import greenlet
  8. from greenlet import getcurrent
  9. from . import TestCase
  10. try:
  11. from contextvars import Context
  12. from contextvars import ContextVar
  13. from contextvars import copy_context
  14. # From the documentation:
  15. #
  16. # Important: Context Variables should be created at the top module
  17. # level and never in closures. Context objects hold strong
  18. # references to context variables which prevents context variables
  19. # from being properly garbage collected.
  20. ID_VAR = ContextVar("id", default=None)
  21. VAR_VAR = ContextVar("var", default=None)
  22. ContextVar = None
  23. except ImportError:
  24. Context = ContextVar = copy_context = None
  25. # We don't support testing if greenlet's built-in context var support is disabled.
  26. @skipUnless(Context is not None, "ContextVar not supported")
  27. class ContextVarsTests(TestCase):
  28. def _new_ctx_run(self, *args, **kwargs):
  29. return copy_context().run(*args, **kwargs)
  30. def _increment(self, greenlet_id, callback, counts, expect):
  31. ctx_var = ID_VAR
  32. if expect is None:
  33. self.assertIsNone(ctx_var.get())
  34. else:
  35. self.assertEqual(ctx_var.get(), expect)
  36. ctx_var.set(greenlet_id)
  37. for _ in range(2):
  38. counts[ctx_var.get()] += 1
  39. callback()
  40. def _test_context(self, propagate_by):
  41. ID_VAR.set(0)
  42. callback = getcurrent().switch
  43. counts = dict((i, 0) for i in range(5))
  44. lets = [
  45. greenlet(partial(
  46. partial(
  47. copy_context().run,
  48. self._increment
  49. ) if propagate_by == "run" else self._increment,
  50. greenlet_id=i,
  51. callback=callback,
  52. counts=counts,
  53. expect=(
  54. i - 1 if propagate_by == "share" else
  55. 0 if propagate_by in ("set", "run") else None
  56. )
  57. ))
  58. for i in range(1, 5)
  59. ]
  60. for let in lets:
  61. if propagate_by == "set":
  62. let.gr_context = copy_context()
  63. elif propagate_by == "share":
  64. let.gr_context = getcurrent().gr_context
  65. for i in range(2):
  66. counts[ID_VAR.get()] += 1
  67. for let in lets:
  68. let.switch()
  69. if propagate_by == "run":
  70. # Must leave each context.run() in reverse order of entry
  71. for let in reversed(lets):
  72. let.switch()
  73. else:
  74. # No context.run(), so fine to exit in any order.
  75. for let in lets:
  76. let.switch()
  77. for let in lets:
  78. self.assertTrue(let.dead)
  79. # When using run(), we leave the run() as the greenlet dies,
  80. # and there's no context "underneath". When not using run(),
  81. # gr_context still reflects the context the greenlet was
  82. # running in.
  83. if propagate_by == 'run':
  84. self.assertIsNone(let.gr_context)
  85. else:
  86. self.assertIsNotNone(let.gr_context)
  87. if propagate_by == "share":
  88. self.assertEqual(counts, {0: 1, 1: 1, 2: 1, 3: 1, 4: 6})
  89. else:
  90. self.assertEqual(set(counts.values()), set([2]))
  91. def test_context_propagated_by_context_run(self):
  92. self._new_ctx_run(self._test_context, "run")
  93. def test_context_propagated_by_setting_attribute(self):
  94. self._new_ctx_run(self._test_context, "set")
  95. def test_context_not_propagated(self):
  96. self._new_ctx_run(self._test_context, None)
  97. def test_context_shared(self):
  98. self._new_ctx_run(self._test_context, "share")
  99. def test_break_ctxvars(self):
  100. let1 = greenlet(copy_context().run)
  101. let2 = greenlet(copy_context().run)
  102. let1.switch(getcurrent().switch)
  103. let2.switch(getcurrent().switch)
  104. # Since let2 entered the current context and let1 exits its own, the
  105. # interpreter emits:
  106. # RuntimeError: cannot exit context: thread state references a different context object
  107. let1.switch()
  108. def test_not_broken_if_using_attribute_instead_of_context_run(self):
  109. let1 = greenlet(getcurrent().switch)
  110. let2 = greenlet(getcurrent().switch)
  111. let1.gr_context = copy_context()
  112. let2.gr_context = copy_context()
  113. let1.switch()
  114. let2.switch()
  115. let1.switch()
  116. let2.switch()
  117. def test_context_assignment_while_running(self):
  118. # pylint:disable=too-many-statements
  119. ID_VAR.set(None)
  120. def target():
  121. self.assertIsNone(ID_VAR.get())
  122. self.assertIsNone(gr.gr_context)
  123. # Context is created on first use
  124. ID_VAR.set(1)
  125. self.assertIsInstance(gr.gr_context, Context)
  126. self.assertEqual(ID_VAR.get(), 1)
  127. self.assertEqual(gr.gr_context[ID_VAR], 1)
  128. # Clearing the context makes it get re-created as another
  129. # empty context when next used
  130. old_context = gr.gr_context
  131. gr.gr_context = None # assign None while running
  132. self.assertIsNone(ID_VAR.get())
  133. self.assertIsNone(gr.gr_context)
  134. ID_VAR.set(2)
  135. self.assertIsInstance(gr.gr_context, Context)
  136. self.assertEqual(ID_VAR.get(), 2)
  137. self.assertEqual(gr.gr_context[ID_VAR], 2)
  138. new_context = gr.gr_context
  139. getcurrent().parent.switch((old_context, new_context))
  140. # parent switches us back to old_context
  141. self.assertEqual(ID_VAR.get(), 1)
  142. gr.gr_context = new_context # assign non-None while running
  143. self.assertEqual(ID_VAR.get(), 2)
  144. getcurrent().parent.switch()
  145. # parent switches us back to no context
  146. self.assertIsNone(ID_VAR.get())
  147. self.assertIsNone(gr.gr_context)
  148. gr.gr_context = old_context
  149. self.assertEqual(ID_VAR.get(), 1)
  150. getcurrent().parent.switch()
  151. # parent switches us back to no context
  152. self.assertIsNone(ID_VAR.get())
  153. self.assertIsNone(gr.gr_context)
  154. gr = greenlet(target)
  155. with self.assertRaisesRegex(AttributeError, "can't delete context attribute"):
  156. del gr.gr_context
  157. self.assertIsNone(gr.gr_context)
  158. old_context, new_context = gr.switch()
  159. self.assertIs(new_context, gr.gr_context)
  160. self.assertEqual(old_context[ID_VAR], 1)
  161. self.assertEqual(new_context[ID_VAR], 2)
  162. self.assertEqual(new_context.run(ID_VAR.get), 2)
  163. gr.gr_context = old_context # assign non-None while suspended
  164. gr.switch()
  165. self.assertIs(gr.gr_context, new_context)
  166. gr.gr_context = None # assign None while suspended
  167. gr.switch()
  168. self.assertIs(gr.gr_context, old_context)
  169. gr.gr_context = None
  170. gr.switch()
  171. self.assertIsNone(gr.gr_context)
  172. # Make sure there are no reference leaks
  173. gr = None
  174. gc.collect()
  175. self.assertEqual(sys.getrefcount(old_context), 2)
  176. self.assertEqual(sys.getrefcount(new_context), 2)
  177. def test_context_assignment_different_thread(self):
  178. import threading
  179. VAR_VAR.set(None)
  180. ctx = Context()
  181. is_running = threading.Event()
  182. should_suspend = threading.Event()
  183. did_suspend = threading.Event()
  184. should_exit = threading.Event()
  185. holder = []
  186. def greenlet_in_thread_fn():
  187. VAR_VAR.set(1)
  188. is_running.set()
  189. should_suspend.wait(10)
  190. VAR_VAR.set(2)
  191. getcurrent().parent.switch()
  192. holder.append(VAR_VAR.get())
  193. def thread_fn():
  194. gr = greenlet(greenlet_in_thread_fn)
  195. gr.gr_context = ctx
  196. holder.append(gr)
  197. gr.switch()
  198. did_suspend.set()
  199. should_exit.wait(10)
  200. gr.switch()
  201. del gr
  202. greenlet() # trigger cleanup
  203. thread = threading.Thread(target=thread_fn, daemon=True)
  204. thread.start()
  205. is_running.wait(10)
  206. gr = holder[0]
  207. # Can't access or modify context if the greenlet is running
  208. # in a different thread
  209. with self.assertRaisesRegex(ValueError, "running in a different"):
  210. getattr(gr, 'gr_context')
  211. with self.assertRaisesRegex(ValueError, "running in a different"):
  212. gr.gr_context = None
  213. should_suspend.set()
  214. did_suspend.wait(10)
  215. # OK to access and modify context if greenlet is suspended
  216. self.assertIs(gr.gr_context, ctx)
  217. self.assertEqual(gr.gr_context[VAR_VAR], 2)
  218. gr.gr_context = None
  219. should_exit.set()
  220. thread.join(10)
  221. self.assertEqual(holder, [gr, None])
  222. # Context can still be accessed/modified when greenlet is dead:
  223. self.assertIsNone(gr.gr_context)
  224. gr.gr_context = ctx
  225. self.assertIs(gr.gr_context, ctx)
  226. # Otherwise we leak greenlets on some platforms.
  227. # XXX: Should be able to do this automatically
  228. del holder[:]
  229. gr = None
  230. thread = None
  231. def test_context_assignment_wrong_type(self):
  232. g = greenlet()
  233. with self.assertRaisesRegex(TypeError,
  234. "greenlet context must be a contextvars.Context or None"):
  235. g.gr_context = self
  236. @skipIf(Context is not None, "ContextVar supported")
  237. class NoContextVarsTests(TestCase):
  238. def test_contextvars_errors(self):
  239. let1 = greenlet(getcurrent().switch)
  240. self.assertFalse(hasattr(let1, 'gr_context'))
  241. with self.assertRaises(AttributeError):
  242. getattr(let1, 'gr_context')
  243. with self.assertRaises(AttributeError):
  244. let1.gr_context = None
  245. let1.switch()
  246. with self.assertRaises(AttributeError):
  247. getattr(let1, 'gr_context')
  248. with self.assertRaises(AttributeError):
  249. let1.gr_context = None
  250. del let1