local.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. """
  2. Greenlet-local objects.
  3. This module is based on `_threading_local.py`__ from the standard
  4. library of Python 3.4.
  5. __ https://github.com/python/cpython/blob/3.4/Lib/_threading_local.py
  6. Greenlet-local objects support the management of greenlet-local data.
  7. If you have data that you want to be local to a greenlet, simply create
  8. a greenlet-local object and use its attributes:
  9. >>> mydata = local()
  10. >>> mydata.number = 42
  11. >>> mydata.number
  12. 42
  13. You can also access the local-object's dictionary:
  14. >>> mydata.__dict__
  15. {'number': 42}
  16. >>> mydata.__dict__.setdefault('widgets', [])
  17. []
  18. >>> mydata.widgets
  19. []
  20. What's important about greenlet-local objects is that their data are
  21. local to a greenlet. If we access the data in a different greenlet:
  22. >>> log = []
  23. >>> def f():
  24. ... items = list(mydata.__dict__.items())
  25. ... items.sort()
  26. ... log.append(items)
  27. ... mydata.number = 11
  28. ... log.append(mydata.number)
  29. >>> greenlet = gevent.spawn(f)
  30. >>> greenlet.join()
  31. >>> log
  32. [[], 11]
  33. we get different data. Furthermore, changes made in the other greenlet
  34. don't affect data seen in this greenlet:
  35. >>> mydata.number
  36. 42
  37. Of course, values you get from a local object, including a __dict__
  38. attribute, are for whatever greenlet was current at the time the
  39. attribute was read. For that reason, you generally don't want to save
  40. these values across greenlets, as they apply only to the greenlet they
  41. came from.
  42. You can create custom local objects by subclassing the local class:
  43. >>> class MyLocal(local):
  44. ... number = 2
  45. ... initialized = False
  46. ... def __init__(self, **kw):
  47. ... if self.initialized:
  48. ... raise SystemError('__init__ called too many times')
  49. ... self.initialized = True
  50. ... self.__dict__.update(kw)
  51. ... def squared(self):
  52. ... return self.number ** 2
  53. This can be useful to support default values, methods and
  54. initialization. Note that if you define an __init__ method, it will be
  55. called each time the local object is used in a separate greenlet. This
  56. is necessary to initialize each greenlet's dictionary.
  57. Now if we create a local object:
  58. >>> mydata = MyLocal(color='red')
  59. Now we have a default number:
  60. >>> mydata.number
  61. 2
  62. an initial color:
  63. >>> mydata.color
  64. 'red'
  65. >>> del mydata.color
  66. And a method that operates on the data:
  67. >>> mydata.squared()
  68. 4
  69. As before, we can access the data in a separate greenlet:
  70. >>> log = []
  71. >>> greenlet = gevent.spawn(f)
  72. >>> greenlet.join()
  73. >>> log
  74. [[('color', 'red'), ('initialized', True)], 11]
  75. without affecting this greenlet's data:
  76. >>> mydata.number
  77. 2
  78. >>> mydata.color
  79. Traceback (most recent call last):
  80. ...
  81. AttributeError: 'MyLocal' object has no attribute 'color'
  82. Note that subclasses can define slots, but they are not greenlet
  83. local. They are shared across greenlets::
  84. >>> class MyLocal(local):
  85. ... __slots__ = 'number'
  86. >>> mydata = MyLocal()
  87. >>> mydata.number = 42
  88. >>> mydata.color = 'red'
  89. So, the separate greenlet:
  90. >>> greenlet = gevent.spawn(f)
  91. >>> greenlet.join()
  92. affects what we see:
  93. >>> mydata.number
  94. 11
  95. >>> del mydata
  96. .. versionchanged:: 1.1a2
  97. Update the implementation to match Python 3.4 instead of Python 2.5.
  98. This results in locals being eligible for garbage collection as soon
  99. as their greenlet exits.
  100. """
  101. from copy import copy
  102. from weakref import ref
  103. from contextlib import contextmanager
  104. from gevent.hub import getcurrent
  105. from gevent._compat import PYPY
  106. from gevent.lock import RLock
  107. __all__ = ["local"]
  108. class _wrefdict(dict):
  109. """A dict that can be weak referenced"""
  110. class _localimpl(object):
  111. """A class managing thread-local dicts"""
  112. __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'
  113. def __init__(self):
  114. # The key used in the Thread objects' attribute dicts.
  115. # We keep it a string for speed but make it unlikely to clash with
  116. # a "real" attribute.
  117. self.key = '_threading_local._localimpl.' + str(id(self))
  118. # { id(Thread) -> (ref(Thread), thread-local dict) }
  119. self.dicts = _wrefdict()
  120. def get_dict(self):
  121. """Return the dict for the current thread. Raises KeyError if none
  122. defined."""
  123. thread = getcurrent()
  124. return self.dicts[id(thread)][1]
  125. def create_dict(self):
  126. """Create a new dict for the current thread, and return it."""
  127. localdict = {}
  128. key = self.key
  129. thread = getcurrent()
  130. idt = id(thread)
  131. # If we are working with a gevent.greenlet.Greenlet, we can
  132. # pro-actively clear out with a link. Use rawlink to avoid
  133. # spawning any more greenlets
  134. try:
  135. rawlink = thread.rawlink
  136. except AttributeError:
  137. # Otherwise we need to do it with weak refs
  138. def local_deleted(_, key=key):
  139. # When the localimpl is deleted, remove the thread attribute.
  140. thread = wrthread()
  141. if thread is not None:
  142. del thread.__dict__[key]
  143. def thread_deleted(_, idt=idt):
  144. # When the thread is deleted, remove the local dict.
  145. # Note that this is suboptimal if the thread object gets
  146. # caught in a reference loop. We would like to be called
  147. # as soon as the OS-level thread ends instead.
  148. _local = wrlocal()
  149. if _local is not None:
  150. _local.dicts.pop(idt, None)
  151. wrlocal = ref(self, local_deleted)
  152. wrthread = ref(thread, thread_deleted)
  153. thread.__dict__[key] = wrlocal
  154. else:
  155. wrdicts = ref(self.dicts)
  156. def clear(_):
  157. dicts = wrdicts()
  158. if dicts:
  159. dicts.pop(idt, None)
  160. rawlink(clear)
  161. wrthread = None
  162. self.dicts[idt] = wrthread, localdict
  163. return localdict
  164. @contextmanager
  165. def _patch(self):
  166. impl = object.__getattribute__(self, '_local__impl')
  167. orig_dct = object.__getattribute__(self, '__dict__')
  168. try:
  169. dct = impl.get_dict()
  170. except KeyError:
  171. # it's OK to acquire the lock here and not earlier, because the above code won't switch out
  172. # however, subclassed __init__ might switch, so we do need to acquire the lock here
  173. dct = impl.create_dict()
  174. args, kw = impl.localargs
  175. with impl.locallock:
  176. self.__init__(*args, **kw)
  177. with impl.locallock:
  178. object.__setattr__(self, '__dict__', dct)
  179. yield
  180. object.__setattr__(self, '__dict__', orig_dct)
  181. class local(object):
  182. """
  183. An object whose attributes are greenlet-local.
  184. """
  185. __slots__ = '_local__impl', '__dict__'
  186. def __new__(cls, *args, **kw):
  187. if args or kw:
  188. if (PYPY and cls.__init__ == object.__init__) or (not PYPY and cls.__init__ is object.__init__):
  189. raise TypeError("Initialization arguments are not supported")
  190. self = object.__new__(cls)
  191. impl = _localimpl()
  192. impl.localargs = (args, kw)
  193. impl.locallock = RLock()
  194. object.__setattr__(self, '_local__impl', impl)
  195. # We need to create the thread dict in anticipation of
  196. # __init__ being called, to make sure we don't call it
  197. # again ourselves.
  198. impl.create_dict()
  199. return self
  200. def __getattribute__(self, name):
  201. with _patch(self):
  202. return object.__getattribute__(self, name)
  203. def __setattr__(self, name, value):
  204. if name == '__dict__':
  205. raise AttributeError(
  206. "%r object attribute '__dict__' is read-only"
  207. % self.__class__.__name__)
  208. with _patch(self):
  209. return object.__setattr__(self, name, value)
  210. def __delattr__(self, name):
  211. if name == '__dict__':
  212. raise AttributeError(
  213. "%r object attribute '__dict__' is read-only"
  214. % self.__class__.__name__)
  215. with _patch(self):
  216. return object.__delattr__(self, name)
  217. def __copy__(self):
  218. impl = object.__getattribute__(self, '_local__impl')
  219. current = getcurrent()
  220. currentId = id(current)
  221. d = impl.get_dict()
  222. duplicate = copy(d)
  223. cls = type(self)
  224. if (PYPY and cls.__init__ != object.__init__) or (not PYPY and cls.__init__ is not object.__init__):
  225. args, kw = impl.localargs
  226. instance = cls(*args, **kw)
  227. else:
  228. instance = cls()
  229. new_impl = object.__getattribute__(instance, '_local__impl')
  230. tpl = new_impl.dicts[currentId]
  231. new_impl.dicts[currentId] = (tpl[0], duplicate)
  232. return instance