PageRenderTime 44ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/rpython/rlib/rthread.py

https://bitbucket.org/pypy/pypy/
Python | 446 lines | 398 code | 15 blank | 33 comment | 8 complexity | d57f9c03382b9b0af0981d58298ffdec MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. from rpython.rtyper.lltypesystem import rffi, lltype, llmemory
  2. from rpython.translator.tool.cbuild import ExternalCompilationInfo
  3. from rpython.translator import cdir
  4. import py, sys
  5. from rpython.rlib import jit, rgc
  6. from rpython.rlib.debug import ll_assert
  7. from rpython.rlib.objectmodel import we_are_translated, specialize
  8. from rpython.rlib.objectmodel import CDefinedIntSymbolic
  9. from rpython.rtyper.lltypesystem.lloperation import llop
  10. from rpython.rtyper.tool import rffi_platform
  11. from rpython.rtyper.extregistry import ExtRegistryEntry
  12. class RThreadError(Exception):
  13. pass
  14. error = RThreadError
  15. translator_c_dir = py.path.local(cdir)
  16. eci = ExternalCompilationInfo(
  17. includes = ['src/thread.h'],
  18. separate_module_files = [translator_c_dir / 'src' / 'thread.c'],
  19. include_dirs = [translator_c_dir],
  20. )
  21. def llexternal(name, args, result, **kwds):
  22. kwds.setdefault('sandboxsafe', True)
  23. return rffi.llexternal(name, args, result, compilation_info=eci,
  24. **kwds)
  25. def _emulated_start_new_thread(func):
  26. "NOT_RPYTHON"
  27. import thread
  28. try:
  29. ident = thread.start_new_thread(func, ())
  30. except thread.error:
  31. ident = -1
  32. return rffi.cast(rffi.LONG, ident)
  33. CALLBACK = lltype.Ptr(lltype.FuncType([], lltype.Void))
  34. c_thread_start = llexternal('RPyThreadStart', [CALLBACK], rffi.LONG,
  35. _callable=_emulated_start_new_thread,
  36. releasegil=True) # release the GIL, but most
  37. # importantly, reacquire it
  38. # around the callback
  39. TLOCKP = rffi.COpaquePtr('struct RPyOpaque_ThreadLock',
  40. compilation_info=eci)
  41. TLOCKP_SIZE = rffi_platform.sizeof('struct RPyOpaque_ThreadLock', eci)
  42. c_thread_lock_init = llexternal('RPyThreadLockInit', [TLOCKP], rffi.INT,
  43. releasegil=False) # may add in a global list
  44. c_thread_lock_dealloc_NOAUTO = llexternal('RPyOpaqueDealloc_ThreadLock',
  45. [TLOCKP], lltype.Void,
  46. _nowrapper=True)
  47. c_thread_acquirelock = llexternal('RPyThreadAcquireLock', [TLOCKP, rffi.INT],
  48. rffi.INT,
  49. releasegil=True) # release the GIL
  50. c_thread_acquirelock_timed = llexternal('RPyThreadAcquireLockTimed',
  51. [TLOCKP, rffi.LONGLONG, rffi.INT],
  52. rffi.INT,
  53. releasegil=True) # release the GIL
  54. c_thread_releaselock = llexternal('RPyThreadReleaseLock', [TLOCKP],
  55. lltype.Signed,
  56. _nowrapper=True) # *don't* release the GIL
  57. # another set of functions, this time in versions that don't cause the
  58. # GIL to be released. Used to be there to handle the GIL lock itself,
  59. # but that was changed (see rgil.py). Now here for performance only.
  60. c_thread_acquirelock_NOAUTO = llexternal('RPyThreadAcquireLock',
  61. [TLOCKP, rffi.INT], rffi.INT,
  62. _nowrapper=True)
  63. c_thread_acquirelock_timed_NOAUTO = llexternal('RPyThreadAcquireLockTimed',
  64. [TLOCKP, rffi.LONGLONG, rffi.INT],
  65. rffi.INT, _nowrapper=True)
  66. c_thread_releaselock_NOAUTO = c_thread_releaselock
  67. def allocate_lock():
  68. return Lock(allocate_ll_lock())
  69. @specialize.arg(0)
  70. def ll_start_new_thread(func):
  71. from rpython.rlib import rgil
  72. _check_thread_enabled()
  73. rgil.allocate()
  74. # ^^^ convenience: any RPython program which uses explicitly
  75. # rthread.start_new_thread() will initialize the GIL at that
  76. # point.
  77. ident = c_thread_start(func)
  78. if ident == -1:
  79. raise error("can't start new thread")
  80. return ident
  81. # wrappers...
  82. def get_ident():
  83. if we_are_translated():
  84. return tlfield_thread_ident.getraw()
  85. else:
  86. import thread
  87. return thread.get_ident()
  88. def get_or_make_ident():
  89. if we_are_translated():
  90. return tlfield_thread_ident.get_or_make_raw()
  91. else:
  92. import thread
  93. return thread.get_ident()
  94. @specialize.arg(0)
  95. def start_new_thread(x, y):
  96. """In RPython, no argument can be passed. You have to use global
  97. variables to pass information to the new thread. That's not very
  98. nice, but at least it avoids some levels of GC issues.
  99. """
  100. assert len(y) == 0
  101. return rffi.cast(lltype.Signed, ll_start_new_thread(x))
  102. class DummyLock(object):
  103. def acquire(self, flag):
  104. return True
  105. def release(self):
  106. pass
  107. def _freeze_(self):
  108. return True
  109. def __enter__(self):
  110. pass
  111. def __exit__(self, *args):
  112. pass
  113. dummy_lock = DummyLock()
  114. class Lock(object):
  115. """ Container for low-level implementation
  116. of a lock object
  117. """
  118. _immutable_fields_ = ["_lock"]
  119. def __init__(self, ll_lock):
  120. self._lock = ll_lock
  121. def acquire(self, flag):
  122. if flag:
  123. c_thread_acquirelock(self._lock, 1)
  124. return True
  125. else:
  126. res = c_thread_acquirelock_timed_NOAUTO(
  127. self._lock,
  128. rffi.cast(rffi.LONGLONG, 0),
  129. rffi.cast(rffi.INT, 0))
  130. res = rffi.cast(lltype.Signed, res)
  131. return bool(res)
  132. def acquire_timed(self, timeout):
  133. """Timeout is in microseconds. Returns 0 in case of failure,
  134. 1 in case it works, 2 if interrupted by a signal."""
  135. res = c_thread_acquirelock_timed(self._lock, timeout, 1)
  136. res = rffi.cast(lltype.Signed, res)
  137. return res
  138. def release(self):
  139. if c_thread_releaselock(self._lock) != 0:
  140. raise error("the lock was not previously acquired")
  141. def __del__(self):
  142. if free_ll_lock is None: # happens when tests are shutting down
  143. return
  144. free_ll_lock(self._lock)
  145. def __enter__(self):
  146. self.acquire(True)
  147. def __exit__(self, *args):
  148. self.release()
  149. def _cleanup_(self):
  150. raise Exception("seeing a prebuilt rpython.rlib.rthread.Lock instance")
  151. def _check_thread_enabled():
  152. pass
  153. class Entry(ExtRegistryEntry):
  154. _about_ = _check_thread_enabled
  155. def compute_result_annotation(self):
  156. translator = self.bookkeeper.annotator.translator
  157. if not translator.config.translation.thread:
  158. raise Exception(
  159. "this RPython program uses threads: translate with '--thread'")
  160. def specialize_call(self, hop):
  161. hop.exception_cannot_occur()
  162. # ____________________________________________________________
  163. #
  164. # Stack size
  165. get_stacksize = llexternal('RPyThreadGetStackSize', [], lltype.Signed)
  166. set_stacksize = llexternal('RPyThreadSetStackSize', [lltype.Signed],
  167. lltype.Signed)
  168. # ____________________________________________________________
  169. #
  170. # Hack
  171. thread_after_fork = llexternal('RPyThreadAfterFork', [], lltype.Void)
  172. # ____________________________________________________________
  173. #
  174. # GIL support wrappers
  175. null_ll_lock = lltype.nullptr(TLOCKP.TO)
  176. def allocate_ll_lock():
  177. # track_allocation=False here; be careful to lltype.free() it. The
  178. # reason it is set to False is that we get it from all app-level
  179. # lock objects, as well as from the GIL, which exists at shutdown.
  180. ll_lock = lltype.malloc(TLOCKP.TO, flavor='raw', track_allocation=False)
  181. res = c_thread_lock_init(ll_lock)
  182. if rffi.cast(lltype.Signed, res) <= 0:
  183. lltype.free(ll_lock, flavor='raw', track_allocation=False)
  184. raise error("out of resources")
  185. # Add some memory pressure for the size of the lock because it is an
  186. # Opaque object
  187. rgc.add_memory_pressure(TLOCKP_SIZE)
  188. return ll_lock
  189. def free_ll_lock(ll_lock):
  190. acquire_NOAUTO(ll_lock, False)
  191. release_NOAUTO(ll_lock)
  192. c_thread_lock_dealloc_NOAUTO(ll_lock)
  193. lltype.free(ll_lock, flavor='raw', track_allocation=False)
  194. def acquire_NOAUTO(ll_lock, flag):
  195. flag = rffi.cast(rffi.INT, int(flag))
  196. res = c_thread_acquirelock_NOAUTO(ll_lock, flag)
  197. res = rffi.cast(lltype.Signed, res)
  198. return bool(res)
  199. def release_NOAUTO(ll_lock):
  200. if not we_are_translated():
  201. ll_assert(not acquire_NOAUTO(ll_lock, False), "NOAUTO lock not held!")
  202. c_thread_releaselock_NOAUTO(ll_lock)
  203. # ____________________________________________________________
  204. #
  205. # Thread integration.
  206. # These are five completely ad-hoc operations at the moment.
  207. @jit.dont_look_inside
  208. def gc_thread_run():
  209. """To call whenever the current thread (re-)acquired the GIL.
  210. """
  211. if we_are_translated():
  212. llop.gc_thread_run(lltype.Void)
  213. gc_thread_run._always_inline_ = True
  214. @jit.dont_look_inside
  215. def gc_thread_start():
  216. """To call at the beginning of a new thread.
  217. """
  218. if we_are_translated():
  219. llop.gc_thread_start(lltype.Void)
  220. @jit.dont_look_inside
  221. def gc_thread_die():
  222. """To call just before the final GIL release done by a dying
  223. thread. After a thread_die(), no more gc operation should
  224. occur in this thread.
  225. """
  226. if we_are_translated():
  227. llop.gc_thread_die(lltype.Void)
  228. gc_thread_die._always_inline_ = True
  229. @jit.dont_look_inside
  230. def gc_thread_before_fork():
  231. """To call just before fork(). Prepares for forking, after
  232. which only the current thread will be alive.
  233. """
  234. if we_are_translated():
  235. return llop.gc_thread_before_fork(llmemory.Address)
  236. else:
  237. return llmemory.NULL
  238. @jit.dont_look_inside
  239. def gc_thread_after_fork(result_of_fork, opaqueaddr):
  240. """To call just after fork().
  241. """
  242. if we_are_translated():
  243. llop.gc_thread_after_fork(lltype.Void, result_of_fork, opaqueaddr)
  244. else:
  245. assert opaqueaddr == llmemory.NULL
  246. # ____________________________________________________________
  247. #
  248. # Thread-locals.
  249. class ThreadLocalField(object):
  250. def __init__(self, FIELDTYPE, fieldname, loop_invariant=False):
  251. "NOT_RPYTHON: must be prebuilt"
  252. try:
  253. from thread import _local
  254. except ImportError:
  255. class _local(object):
  256. pass
  257. self.FIELDTYPE = FIELDTYPE
  258. self.fieldname = fieldname
  259. self.local = _local() # <- NOT_RPYTHON
  260. zero = rffi.cast(FIELDTYPE, 0)
  261. offset = CDefinedIntSymbolic('RPY_TLOFS_%s' % self.fieldname,
  262. default='?')
  263. offset.loop_invariant = loop_invariant
  264. self._offset = offset
  265. def getraw():
  266. if we_are_translated():
  267. _threadlocalref_seeme(self)
  268. return llop.threadlocalref_get(FIELDTYPE, offset)
  269. else:
  270. return getattr(self.local, 'rawvalue', zero)
  271. @jit.dont_look_inside
  272. def get_or_make_raw():
  273. if we_are_translated():
  274. _threadlocalref_seeme(self)
  275. addr = llop.threadlocalref_addr(llmemory.Address)
  276. return llop.raw_load(FIELDTYPE, addr, offset)
  277. else:
  278. return getattr(self.local, 'rawvalue', zero)
  279. @jit.dont_look_inside
  280. def setraw(value):
  281. if we_are_translated():
  282. _threadlocalref_seeme(self)
  283. addr = llop.threadlocalref_addr(llmemory.Address)
  284. llop.raw_store(lltype.Void, addr, offset, value)
  285. else:
  286. self.local.rawvalue = value
  287. def getoffset():
  288. _threadlocalref_seeme(self)
  289. return offset
  290. self.getraw = getraw
  291. self.get_or_make_raw = get_or_make_raw
  292. self.setraw = setraw
  293. self.getoffset = getoffset
  294. def _freeze_(self):
  295. return True
  296. class ThreadLocalReference(ThreadLocalField):
  297. # A thread-local that points to an object. The object stored in such
  298. # a thread-local is kept alive as long as the thread is not finished
  299. # (but only with our own GCs! it seems not to work with Boehm...)
  300. # (also, on Windows, if you're not making a DLL but an EXE, it will
  301. # leak the objects when a thread finishes; see threadlocal.c.)
  302. _COUNT = 1
  303. def __init__(self, Cls, loop_invariant=False):
  304. "NOT_RPYTHON: must be prebuilt"
  305. self.Cls = Cls
  306. unique_id = ThreadLocalReference._COUNT
  307. ThreadLocalReference._COUNT += 1
  308. ThreadLocalField.__init__(self, lltype.Signed, 'tlref%d' % unique_id,
  309. loop_invariant=loop_invariant)
  310. setraw = self.setraw
  311. offset = self._offset
  312. def get():
  313. if we_are_translated():
  314. from rpython.rtyper import rclass
  315. from rpython.rtyper.annlowlevel import cast_base_ptr_to_instance
  316. _threadlocalref_seeme(self)
  317. ptr = llop.threadlocalref_get(rclass.OBJECTPTR, offset)
  318. return cast_base_ptr_to_instance(Cls, ptr)
  319. else:
  320. return getattr(self.local, 'value', None)
  321. @jit.dont_look_inside
  322. def set(value):
  323. assert isinstance(value, Cls) or value is None
  324. if we_are_translated():
  325. from rpython.rtyper.annlowlevel import cast_instance_to_gcref
  326. gcref = cast_instance_to_gcref(value)
  327. value = lltype.cast_ptr_to_int(gcref)
  328. setraw(value)
  329. rgc.register_custom_trace_hook(TRACETLREF, _lambda_trace_tlref)
  330. rgc.ll_writebarrier(_tracetlref_obj)
  331. else:
  332. self.local.value = value
  333. self.get = get
  334. self.set = set
  335. def _trace_tlref(gc, obj, callback, arg):
  336. p = llmemory.NULL
  337. llop.threadlocalref_acquire(lltype.Void)
  338. while True:
  339. p = llop.threadlocalref_enum(llmemory.Address, p)
  340. if not p:
  341. break
  342. gc._trace_callback(callback, arg, p + offset)
  343. llop.threadlocalref_release(lltype.Void)
  344. _lambda_trace_tlref = lambda: _trace_tlref
  345. TRACETLREF = lltype.GcStruct('TRACETLREF')
  346. _tracetlref_obj = lltype.malloc(TRACETLREF, immortal=True)
  347. @staticmethod
  348. def automatic_keepalive(config):
  349. """Returns True if translated with a GC that keeps alive
  350. the set() value until the end of the thread. Returns False
  351. if you need to keep it alive yourself (but in that case, you
  352. should also reset it to None before the thread finishes).
  353. """
  354. return (config.translation.gctransformer == "framework" and
  355. # see translator/c/src/threadlocal.c for the following line
  356. (not _win32 or config.translation.shared))
  357. tlfield_thread_ident = ThreadLocalField(lltype.Signed, "thread_ident",
  358. loop_invariant=True)
  359. tlfield_p_errno = ThreadLocalField(rffi.CArrayPtr(rffi.INT), "p_errno",
  360. loop_invariant=True)
  361. tlfield_rpy_errno = ThreadLocalField(rffi.INT, "rpy_errno")
  362. tlfield_alt_errno = ThreadLocalField(rffi.INT, "alt_errno")
  363. _win32 = (sys.platform == "win32")
  364. if _win32:
  365. from rpython.rlib import rwin32
  366. tlfield_rpy_lasterror = ThreadLocalField(rwin32.DWORD, "rpy_lasterror")
  367. tlfield_alt_lasterror = ThreadLocalField(rwin32.DWORD, "alt_lasterror")
  368. def _threadlocalref_seeme(field):
  369. "NOT_RPYTHON"
  370. class _Entry(ExtRegistryEntry):
  371. _about_ = _threadlocalref_seeme
  372. def compute_result_annotation(self, s_field):
  373. field = s_field.const
  374. self.bookkeeper.thread_local_fields.add(field)
  375. def specialize_call(self, hop):
  376. hop.exception_cannot_occur()