PageRenderTime 27ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 1ms

/pypy/module/thread/threadlocals.py

https://bitbucket.org/pypy/pypy/
Python | 194 lines | 115 code | 24 blank | 55 comment | 19 complexity | 6056dc8d45ca54bffa794e42876fde16 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. import weakref
  2. from rpython.rlib import rthread, rshrinklist
  3. from rpython.rlib.objectmodel import we_are_translated
  4. from rpython.rlib.rarithmetic import r_ulonglong
  5. from pypy.module.thread.error import wrap_thread_error
  6. from pypy.interpreter.executioncontext import ExecutionContext
  7. ExecutionContext._signals_enabled = 0 # default value
  8. class OSThreadLocals:
  9. """Thread-local storage for OS-level threads.
  10. For memory management, this version depends on explicit notification when
  11. a thread finishes. This works as long as the thread was started by
  12. os_thread.bootstrap()."""
  13. def __init__(self, space):
  14. "NOT_RPYTHON"
  15. #
  16. # This object tracks code that enters and leaves threads.
  17. # There are two APIs. For Python-level threads, we know when
  18. # the thread starts and ends, and we call enter_thread() and
  19. # leave_thread(). In a few other cases, like callbacks, we
  20. # might be running in some never-seen-before thread: in this
  21. # case, the callback logic needs to call try_enter_thread() at
  22. # the start, and if this returns True it needs to call
  23. # leave_thread() at the end.
  24. #
  25. # We implement an optimization for the second case (which only
  26. # works if we translate with a framework GC and with
  27. # rweakref). If try_enter_thread() is called in a
  28. # never-seen-before thread, it still returns False and
  29. # remembers the ExecutionContext with 'self._weaklist'. The
  30. # next time we call try_enter_thread() again in the same
  31. # thread, the ExecutionContext is reused. The optimization is
  32. # not completely invisible to the user: 'thread._local()'
  33. # values will remain. We can argue that it is the correct
  34. # behavior to do that, and the behavior we get if the
  35. # optimization is disabled is buggy (but hard to do better
  36. # then).
  37. #
  38. # 'self._valuedict' is a dict mapping the thread idents to
  39. # ExecutionContexts; it does not list the ExecutionContexts
  40. # which are in 'self._weaklist'. (The latter is more precisely
  41. # a list of AutoFreeECWrapper objects, defined below, which
  42. # each references the ExecutionContext.)
  43. #
  44. self.space = space
  45. self._valuedict = {}
  46. self._cleanup_()
  47. self.raw_thread_local = rthread.ThreadLocalReference(ExecutionContext,
  48. loop_invariant=True)
  49. def can_optimize_with_weaklist(self):
  50. config = self.space.config
  51. return (config.translation.rweakref and
  52. rthread.ThreadLocalReference.automatic_keepalive(config))
  53. def _cleanup_(self):
  54. self._valuedict.clear()
  55. self._weaklist = None
  56. self._mainthreadident = 0
  57. def enter_thread(self, space):
  58. "Notification that the current thread is about to start running."
  59. self._set_ec(space.createexecutioncontext())
  60. def try_enter_thread(self, space):
  61. # common case: the thread-local has already got a value
  62. if self.raw_thread_local.get() is not None:
  63. return False
  64. # Else, make and attach a new ExecutionContext
  65. ec = space.createexecutioncontext()
  66. if not self.can_optimize_with_weaklist():
  67. self._set_ec(ec)
  68. return True
  69. # If can_optimize_with_weaklist(), then 'rthread' keeps the
  70. # thread-local values alive until the end of the thread. Use
  71. # AutoFreeECWrapper as an object with a __del__; when this
  72. # __del__ is called, it means the thread was really finished.
  73. # In this case we don't want leave_thread() to be called
  74. # explicitly, so we return False.
  75. if self._weaklist is None:
  76. self._weaklist = ListECWrappers()
  77. self._weaklist.append(weakref.ref(AutoFreeECWrapper(ec)))
  78. self._set_ec(ec, register_in_valuedict=False)
  79. return False
  80. def _set_ec(self, ec, register_in_valuedict=True):
  81. ident = rthread.get_ident()
  82. if self._mainthreadident == 0 or self._mainthreadident == ident:
  83. ec._signals_enabled = 1 # the main thread is enabled
  84. self._mainthreadident = ident
  85. if register_in_valuedict:
  86. self._valuedict[ident] = ec
  87. self.raw_thread_local.set(ec)
  88. def leave_thread(self, space):
  89. "Notification that the current thread is about to stop."
  90. from pypy.module.thread.os_local import thread_is_stopping
  91. ec = self.get_ec()
  92. if ec is not None:
  93. try:
  94. thread_is_stopping(ec)
  95. finally:
  96. self.raw_thread_local.set(None)
  97. ident = rthread.get_ident()
  98. try:
  99. del self._valuedict[ident]
  100. except KeyError:
  101. pass
  102. def get_ec(self):
  103. ec = self.raw_thread_local.get()
  104. if not we_are_translated():
  105. assert ec is self._valuedict.get(rthread.get_ident(), None)
  106. return ec
  107. def signals_enabled(self):
  108. ec = self.get_ec()
  109. return ec is not None and ec._signals_enabled
  110. def enable_signals(self, space):
  111. ec = self.get_ec()
  112. assert ec is not None
  113. ec._signals_enabled += 1
  114. def disable_signals(self, space):
  115. ec = self.get_ec()
  116. assert ec is not None
  117. new = ec._signals_enabled - 1
  118. if new < 0:
  119. raise wrap_thread_error(space,
  120. "cannot disable signals in thread not enabled for signals")
  121. ec._signals_enabled = new
  122. def getallvalues(self):
  123. if self._weaklist is None:
  124. return self._valuedict
  125. # This logic walks the 'self._weaklist' list and adds the
  126. # ExecutionContexts to 'result'. We are careful in case there
  127. # are two AutoFreeECWrappers in the list which have the same
  128. # 'ident'; in this case we must keep the most recent one (the
  129. # older one should be deleted soon). Moreover, entries in
  130. # self._valuedict have priority because they are never
  131. # outdated.
  132. result = {}
  133. for h in self._weaklist.items():
  134. wrapper = h()
  135. if wrapper is not None and not wrapper.deleted:
  136. result[wrapper.ident] = wrapper.ec
  137. # ^^ this possibly overwrites an older ec
  138. result.update(self._valuedict)
  139. return result
  140. def reinit_threads(self, space):
  141. "Called in the child process after a fork()"
  142. ident = rthread.get_ident()
  143. ec = self.get_ec()
  144. assert ec is not None
  145. old_sig = ec._signals_enabled
  146. if ident != self._mainthreadident:
  147. old_sig += 1
  148. self._cleanup_() # clears self._valuedict
  149. self._mainthreadident = ident
  150. self._set_ec(ec)
  151. ec._signals_enabled = old_sig
  152. class AutoFreeECWrapper(object):
  153. deleted = False
  154. def __init__(self, ec):
  155. # this makes a loop between 'self' and 'ec'. It should not prevent
  156. # the __del__ method here from being called.
  157. self.ec = ec
  158. ec._threadlocals_auto_free = self
  159. self.ident = rthread.get_ident()
  160. def __del__(self):
  161. from pypy.module.thread.os_local import thread_is_stopping
  162. # this is always called in another thread: the thread
  163. # referenced by 'self.ec' has finished at that point, and
  164. # we're just after the GC which finds no more references to
  165. # 'ec' (and thus to 'self').
  166. self.deleted = True
  167. thread_is_stopping(self.ec)
  168. class ListECWrappers(rshrinklist.AbstractShrinkList):
  169. def must_keep(self, wref):
  170. return wref() is not None