PageRenderTime 59ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/pypy/rlib/rcoroutine.py

https://github.com/yasirs/pypy
Python | 352 lines | 336 code | 0 blank | 16 comment | 1 complexity | 848e86b5732c935220b3602d5688ccb0 MD5 | raw file
  1. """
  2. Basic Concept:
  3. --------------
  4. All concurrency is expressed by some means of coroutines.
  5. This is the lowest possible exposable interface.
  6. A coroutine is a structure that controls a sequence
  7. of continuations in time. It contains a frame object
  8. that is a restartable stack chain. This frame object
  9. is updated on every switch.
  10. The frame can be None. Either the coroutine is not yet
  11. bound, or it is the current coroutine of some costate.
  12. See below. XXX rewrite a definition of these terms.
  13. There is always a notation of a "current" and a "last"
  14. coroutine. Current has no frame and represents the
  15. running program. last is needed to keep track of the
  16. coroutine that receives a new frame chain after a switch.
  17. A costate object holds last and current.
  18. There are different coroutine concepts existing in
  19. parallel, like plain interp-level coroutines and
  20. app-level structures like coroutines, greenlets and
  21. tasklets.
  22. Every concept is associated with its own costate object.
  23. This allows for peaceful co-existence of many concepts.
  24. The type of a switch is determined by the target's costate.
  25. """
  26. from pypy.rlib.rstack import yield_current_frame_to_caller
  27. from pypy.rlib.objectmodel import we_are_translated
  28. from pypy.interpreter.error import OperationError
  29. try:
  30. from greenlet import greenlet
  31. main_greenlet = greenlet.getcurrent()
  32. except (ImportError, ValueError):
  33. def greenlet(*args, **kwargs):
  34. raise NotImplementedError("need either greenlets or a translated version of pypy")
  35. class FrameChain(object):
  36. """Greenlet-based emulation of the primitive rstack 'frames' of RPython"""
  37. def __init__(self, thunk=None):
  38. if thunk:
  39. self.greenlet = greenlet(thunk)
  40. else:
  41. self.greenlet = greenlet.getcurrent()
  42. def switch(self):
  43. last = FrameChain()
  44. return self.greenlet.switch(last)
  45. import sys, os
  46. def make_coroutine_classes(baseclass):
  47. class BaseCoState(object):
  48. def __init__(self):
  49. self.current = self.main = None
  50. def __repr__(self):
  51. "NOT_RPYTHON"
  52. # for debugging only
  53. return '<%s current=%r>' % (self.__class__.__name__, self.current)
  54. def update(self, new):
  55. syncstate.leaving = self.current
  56. syncstate.entering = new
  57. self.current = new
  58. frame, new.frame = new.frame, None
  59. return frame
  60. class CoState(BaseCoState):
  61. def __init__(self):
  62. BaseCoState.__init__(self)
  63. self.current = self.main = Coroutine(self)
  64. class CoroutineDamage(SystemError):
  65. pass
  66. class SyncState(object):
  67. def __init__(self):
  68. self.reset()
  69. def reset(self):
  70. self.default_costate = None
  71. self.leaving = None
  72. self.entering = None
  73. self.things_to_do = False
  74. self.temp_exc = None
  75. self.to_delete = []
  76. def switched(self, incoming_frame):
  77. left = syncstate.leaving
  78. entered = syncstate.entering
  79. syncstate.leaving = syncstate.entering = None
  80. if left is not None: # mostly to work around an annotation problem;
  81. # should not really be None
  82. left.frame = incoming_frame
  83. left.goodbye()
  84. if entered is not None:
  85. entered.hello()
  86. if self.things_to_do:
  87. self._do_things_to_do()
  88. def push_exception(self, exc):
  89. self.things_to_do = True
  90. self.temp_exc = exc
  91. def check_for_zombie(self, obj):
  92. return obj in self.to_delete
  93. def postpone_deletion(self, obj):
  94. self.to_delete.append(obj)
  95. self.things_to_do = True
  96. def _do_things_to_do(self):
  97. if self.temp_exc is not None:
  98. # somebody left an unhandled exception and switched to us.
  99. # this both provides default exception handling and the
  100. # way to inject an exception, like CoroutineExit.
  101. e, self.temp_exc = self.temp_exc, None
  102. self.things_to_do = bool(self.to_delete)
  103. raise e
  104. while self.to_delete:
  105. delete, self.to_delete = self.to_delete, []
  106. for obj in delete:
  107. obj.parent = obj.costate.current
  108. obj._kill_finally()
  109. else:
  110. self.things_to_do = False
  111. def _freeze_(self):
  112. self.reset()
  113. return False
  114. syncstate = SyncState()
  115. class CoroutineExit(SystemExit):
  116. # XXX SystemExit's __init__ creates problems in bookkeeper.
  117. def __init__(self):
  118. pass
  119. class AbstractThunk(object):
  120. def call(self):
  121. raise NotImplementedError("abstract base class")
  122. class Coroutine(baseclass):
  123. def __init__(self, state=None):
  124. self.frame = None
  125. if state is None:
  126. state = self._get_default_costate()
  127. self.costate = state
  128. self.parent = None
  129. self.thunk = None
  130. self.coroutine_exit = False
  131. def __repr__(self):
  132. 'NOT_RPYTHON'
  133. # just for debugging
  134. if hasattr(self, '__name__'):
  135. return '<Coro %s frame=%r %s>' % (self.__name__, self.frame, self.thunk is not None)
  136. else:
  137. return '<coro frame=%r %s>' % (self.frame, self.thunk is not None)
  138. def _get_default_costate():
  139. state = syncstate.default_costate
  140. if state is None:
  141. state = syncstate.default_costate = CoState()
  142. return state
  143. _get_default_costate = staticmethod(_get_default_costate)
  144. def _get_default_parent(self):
  145. return self.costate.current
  146. def bind(self, thunk):
  147. assert isinstance(thunk, AbstractThunk)
  148. if self.frame is not None:
  149. raise CoroutineDamage
  150. if self.parent is None:
  151. self.parent = self._get_default_parent()
  152. assert self.parent is not None
  153. self.thunk = thunk
  154. if we_are_translated():
  155. self.frame = self._bind()
  156. else:
  157. self.frame = self._greenlet_bind()
  158. def _greenlet_bind(self):
  159. weak = [self]
  160. def _greenlet_execute(incoming_frame):
  161. try:
  162. chain2go2next = weak[0]._execute(incoming_frame)
  163. except:
  164. # no exception is supposed to get out of _execute()
  165. # better report it directly into the main greenlet then,
  166. # and hidden to prevent catching
  167. main_greenlet.throw(AssertionError(
  168. "unexpected exception out of Coroutine._execute()",
  169. *sys.exc_info()))
  170. assert 0
  171. del weak[0]
  172. greenlet.getcurrent().parent = chain2go2next.greenlet
  173. return None # as the result of the FrameChain.switch()
  174. chain = FrameChain(_greenlet_execute)
  175. return chain
  176. def _bind(self):
  177. state = self.costate
  178. incoming_frame = yield_current_frame_to_caller()
  179. self = state.current
  180. return self._execute(incoming_frame)
  181. def _execute(self, incoming_frame):
  182. state = self.costate
  183. try:
  184. try:
  185. try:
  186. exc = None
  187. thunk = self.thunk
  188. self.thunk = None
  189. syncstate.switched(incoming_frame)
  190. thunk.call()
  191. except Exception, e:
  192. exc = e
  193. raise
  194. finally:
  195. # warning! we must reload the 'self' from the costate,
  196. # because after a clone() the 'self' of both copies
  197. # point to the original!
  198. self = state.current
  199. self.finish(exc)
  200. except CoroutineExit:
  201. pass
  202. except Exception, e:
  203. if self.coroutine_exit is False:
  204. # redirect all unhandled exceptions to the parent
  205. syncstate.push_exception(e)
  206. while self.parent is not None and self.parent.frame is None:
  207. # greenlet behavior is fine
  208. self.parent = self.parent.parent
  209. return state.update(self.parent)
  210. def switch(self):
  211. if self.frame is None:
  212. # considered a programming error.
  213. # greenlets and tasklets have different ideas about this.
  214. raise CoroutineDamage
  215. state = self.costate
  216. incoming_frame = state.update(self).switch()
  217. syncstate.switched(incoming_frame)
  218. def kill(self):
  219. self._kill(CoroutineExit())
  220. def _kill(self, exc):
  221. if self.frame is None:
  222. return
  223. state = self.costate
  224. syncstate.push_exception(exc)
  225. # careful here - if setting self.parent to state.current would
  226. # create a loop, break it. The assumption is that 'self'
  227. # will die, so that state.current's chain of parents can be
  228. # modified to skip 'self' without too many people noticing.
  229. p = state.current
  230. if p is self or self.parent is None:
  231. pass # killing the current of the main - don't change any parent
  232. else:
  233. while p.parent is not None:
  234. if p.parent is self:
  235. p.parent = self.parent
  236. break
  237. p = p.parent
  238. self.parent = state.current
  239. self.switch()
  240. def _kill_finally(self):
  241. try:
  242. self._userdel()
  243. except Exception:
  244. pass # maybe print a warning?
  245. self.kill()
  246. __already_postponed = False
  247. def __del__(self):
  248. # provide the necessary clean-up
  249. # note that AppCoroutine has to take care about this
  250. # as well, including a check for user-supplied __del__.
  251. # Additionally note that in the context of __del__, we are
  252. # not in the position to issue a switch.
  253. # we defer it completely.
  254. # it is necessary to check whether syncstate is None because CPython
  255. # sets it to None when it cleans up the modules, which will lead to
  256. # very strange effects
  257. if not we_are_translated():
  258. # we need to make sure that we postpone each coroutine only once on
  259. # top of CPython, because this resurrects the coroutine and CPython
  260. # calls __del__ again, thus postponing and resurrecting the
  261. # coroutine once more :-(
  262. if self.__already_postponed:
  263. return
  264. self.__already_postponed = True
  265. if syncstate is not None:
  266. syncstate.postpone_deletion(self)
  267. # coroutines need complete control over their __del__ behaviour. In
  268. # particular they need to care about calling space.userdel themselves
  269. handle_del_manually = True
  270. def _userdel(self):
  271. # override this for exposed coros
  272. pass
  273. def is_alive(self):
  274. return self.frame is not None or self is self.costate.current
  275. def is_zombie(self):
  276. return self.frame is not None and syncstate.check_for_zombie(self)
  277. def getcurrent():
  278. costate = Coroutine._get_default_costate()
  279. return costate.current
  280. getcurrent = staticmethod(getcurrent)
  281. def getmain():
  282. costate = Coroutine._get_default_costate()
  283. return costate.main
  284. getmain = staticmethod(getmain)
  285. def hello(self):
  286. "Called when execution is transferred into this coroutine."
  287. def goodbye(self):
  288. "Called just after execution is transferred away from this coroutine."
  289. def finish(self, exc=None):
  290. "stephan forgot me"
  291. return locals()
  292. # _________________________________________________