PageRenderTime 72ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/pypy/doc/stackless.rst

https://bitbucket.org/pypy/pypy/
ReStructuredText | 412 lines | 321 code | 91 blank | 0 comment | 0 complexity | e60aa9e322fb21282ff8373741448651 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. Application-level Stackless features
  2. ====================================
  3. Introduction
  4. ------------
  5. PyPy can expose to its user language features similar to the ones
  6. present in `Stackless Python`_: the ability to write code in a
  7. **massively concurrent style**. (It does not (any more) offer the
  8. ability to run with no `recursion depth limit`_, but the same effect
  9. can be achieved indirectly.)
  10. This feature is based on a custom primitive called a continulet_.
  11. Continulets can be directly used by application code, or it is possible
  12. to write (entirely at app-level) more user-friendly interfaces.
  13. Currently PyPy implements greenlets_ on top of continulets. It also
  14. implements (an approximation of) tasklets and channels, emulating the model
  15. of `Stackless Python`_.
  16. Continulets are extremely light-weight, which means that PyPy should be
  17. able to handle programs containing large amounts of them. However, due
  18. to an implementation restriction, a PyPy compiled with
  19. ``--gcrootfinder=shadowstack`` consumes at least one page of physical
  20. memory (4KB) per live continulet, and half a megabyte of virtual memory
  21. on 32-bit or a complete megabyte on 64-bit. Moreover, the feature is
  22. only available (so far) on x86 and x86-64 CPUs; for other CPUs you need
  23. to add a short page of custom assembler to
  24. :source:`rpython/translator/c/src/stacklet/`.
  25. .. _Stackless Python: http://www.stackless.com
  26. Theory
  27. ------
  28. The fundamental idea is that, at any point in time, the program happens
  29. to run one stack of frames (or one per thread, in case of
  30. multi-threading). To see the stack, start at the top frame and follow
  31. the chain of ``f_back`` until you reach the bottom frame. From the
  32. point of view of one of these frames, it has a ``f_back`` pointing to
  33. another frame (unless it is the bottom frame), and it is itself being
  34. pointed to by another frame (unless it is the top frame).
  35. The theory behind continulets is to literally take the previous sentence
  36. as definition of "an O.K. situation". The trick is that there are
  37. O.K. situations that are more complex than just one stack: you will
  38. always have one stack, but you can also have in addition one or more
  39. detached *cycles* of frames, such that by following the ``f_back`` chain
  40. you run in a circle. But note that these cycles are indeed completely
  41. detached: the top frame (the currently running one) is always the one
  42. which is not the ``f_back`` of anybody else, and it is always the top of
  43. a stack that ends with the bottom frame, never a part of these extra
  44. cycles.
  45. How do you create such cycles? The fundamental operation to do so is to
  46. take two frames and *permute* their ``f_back`` --- i.e. exchange them.
  47. You can permute any two ``f_back`` without breaking the rule of "an O.K.
  48. situation". Say for example that ``f`` is some frame halfway down the
  49. stack, and you permute its ``f_back`` with the ``f_back`` of the top
  50. frame. Then you have removed from the normal stack all intermediate
  51. frames, and turned them into one stand-alone cycle. By doing the same
  52. permutation again you restore the original situation.
  53. In practice, in PyPy, you cannot change the ``f_back`` of an abitrary
  54. frame, but only of frames stored in ``continulets``.
  55. Continulets are internally implemented using stacklets_. Stacklets are a
  56. bit more primitive (they are really one-shot continuations), but that
  57. idea only works in C, not in Python. The basic idea of continulets is
  58. to have at any point in time a complete valid stack; this is important
  59. e.g. to correctly propagate exceptions (and it seems to give meaningful
  60. tracebacks too).
  61. Application level interface
  62. ---------------------------
  63. .. _continulet:
  64. Continulets
  65. ~~~~~~~~~~~
  66. A translated PyPy contains by default a module called ``_continuation``
  67. exporting the type ``continulet``. A ``continulet`` object from this
  68. module is a container that stores a "one-shot continuation". It plays
  69. the role of an extra frame you can insert in the stack, and whose
  70. ``f_back`` can be changed.
  71. To make a continulet object, call ``continulet()`` with a callable and
  72. optional extra arguments.
  73. Later, the first time you ``switch()`` to the continulet, the callable
  74. is invoked with the same continulet object as the extra first argument.
  75. At that point, the one-shot continuation stored in the continulet points
  76. to the caller of ``switch()``. In other words you have a perfectly
  77. normal-looking stack of frames. But when ``switch()`` is called again,
  78. this stored one-shot continuation is exchanged with the current one; it
  79. means that the caller of ``switch()`` is suspended with its continuation
  80. stored in the container, and the old continuation from the continulet
  81. object is resumed.
  82. The most primitive API is actually 'permute()', which just permutes the
  83. one-shot continuation stored in two (or more) continulets.
  84. In more details:
  85. * ``continulet(callable, *args, **kwds)``: make a new continulet.
  86. Like a generator, this only creates it; the ``callable`` is only
  87. actually called the first time it is switched to. It will be
  88. called as follows::
  89. callable(cont, *args, **kwds)
  90. where ``cont`` is the same continulet object.
  91. Note that it is actually ``cont.__init__()`` that binds
  92. the continulet. It is also possible to create a not-bound-yet
  93. continulet by calling explicitly ``continulet.__new__()``, and
  94. only bind it later by calling explicitly ``cont.__init__()``.
  95. * ``cont.switch(value=None, to=None)``: start the continulet if
  96. it was not started yet. Otherwise, store the current continuation
  97. in ``cont``, and activate the target continuation, which is the
  98. one that was previously stored in ``cont``. Note that the target
  99. continuation was itself previously suspended by another call to
  100. ``switch()``; this older ``switch()`` will now appear to return.
  101. The ``value`` argument is any object that is carried to the target
  102. and returned by the target's ``switch()``.
  103. If ``to`` is given, it must be another continulet object. In
  104. that case, performs a "double switch": it switches as described
  105. above to ``cont``, and then immediately switches again to ``to``.
  106. This is different from switching directly to ``to``: the current
  107. continuation gets stored in ``cont``, the old continuation from
  108. ``cont`` gets stored in ``to``, and only then we resume the
  109. execution from the old continuation out of ``to``.
  110. * ``cont.throw(type, value=None, tb=None, to=None)``: similar to
  111. ``switch()``, except that immediately after the switch is done, raise
  112. the given exception in the target.
  113. * ``cont.is_pending()``: return True if the continulet is pending.
  114. This is False when it is not initialized (because we called
  115. ``__new__`` and not ``__init__``) or when it is finished (because
  116. the ``callable()`` returned). When it is False, the continulet
  117. object is empty and cannot be ``switch()``-ed to.
  118. * ``permute(*continulets)``: a global function that permutes the
  119. continuations stored in the given continulets arguments. Mostly
  120. theoretical. In practice, using ``cont.switch()`` is easier and
  121. more efficient than using ``permute()``; the latter does not on
  122. its own change the currently running frame.
  123. Genlets
  124. ~~~~~~~
  125. The ``_continuation`` module also exposes the ``generator`` decorator::
  126. @generator
  127. def f(cont, a, b):
  128. cont.switch(a + b)
  129. cont.switch(a + b + 1)
  130. for i in f(10, 20):
  131. print i
  132. This example prints 30 and 31. The only advantage over using regular
  133. generators is that the generator itself is not limited to ``yield``
  134. statements that must all occur syntactically in the same function.
  135. Instead, we can pass around ``cont``, e.g. to nested sub-functions, and
  136. call ``cont.switch(x)`` from there.
  137. The ``generator`` decorator can also be applied to methods::
  138. class X:
  139. @generator
  140. def f(self, cont, a, b):
  141. ...
  142. Greenlets
  143. ~~~~~~~~~
  144. Greenlets are implemented on top of continulets in :source:`lib_pypy/greenlet.py`.
  145. See the official `documentation of the greenlets`_.
  146. Note that unlike the CPython greenlets, this version does not suffer
  147. from GC issues: if the program "forgets" an unfinished greenlet, it will
  148. always be collected at the next garbage collection.
  149. .. _documentation of the greenlets: http://packages.python.org/greenlet/
  150. Unimplemented features
  151. ~~~~~~~~~~~~~~~~~~~~~~
  152. The following features (present in some past Stackless version of PyPy)
  153. are for the time being not supported any more:
  154. * Coroutines (could be rewritten at app-level)
  155. * Continuing execution of a continulet in a different thread
  156. (but if it is "simple enough", you can pickle it and unpickle it
  157. in the other thread).
  158. * Automatic unlimited stack (must be emulated__ so far)
  159. * Support for other CPUs than x86 and x86-64
  160. .. __: `recursion depth limit`_
  161. We also do not include any of the recent API additions to Stackless
  162. Python, like ``set_atomic()``. Contributions welcome.
  163. Recursion depth limit
  164. ~~~~~~~~~~~~~~~~~~~~~
  165. You can use continulets to emulate the infinite recursion depth present
  166. in Stackless Python and in stackless-enabled older versions of PyPy.
  167. The trick is to start a continulet "early", i.e. when the recursion
  168. depth is very low, and switch to it "later", i.e. when the recursion
  169. depth is high. Example::
  170. from _continuation import continulet
  171. def invoke(_, callable, arg):
  172. return callable(arg)
  173. def bootstrap(c):
  174. # this loop runs forever, at a very low recursion depth
  175. callable, arg = c.switch()
  176. while True:
  177. # start a new continulet from here, and switch to
  178. # it using an "exchange", i.e. a switch with to=.
  179. to = continulet(invoke, callable, arg)
  180. callable, arg = c.switch(to=to)
  181. c = continulet(bootstrap)
  182. c.switch()
  183. def recursive(n):
  184. if n == 0:
  185. return ("ok", n)
  186. if n % 200 == 0:
  187. prev = c.switch((recursive, n - 1))
  188. else:
  189. prev = recursive(n - 1)
  190. return (prev[0], prev[1] + 1)
  191. print recursive(999999) # prints ('ok', 999999)
  192. Note that if you press Ctrl-C while running this example, the traceback
  193. will be built with *all* recursive() calls so far, even if this is more
  194. than the number that can possibly fit in the C stack. These frames are
  195. "overlapping" each other in the sense of the C stack; more precisely,
  196. they are copied out of and into the C stack as needed.
  197. (The example above also makes use of the following general "guideline"
  198. to help newcomers write continulets: in ``bootstrap(c)``, only call
  199. methods on ``c``, not on another continulet object. That's why we wrote
  200. ``c.switch(to=to)`` and not ``to.switch()``, which would mess up the
  201. state. This is however just a guideline; in general we would recommend
  202. to use other interfaces like genlets and greenlets.)
  203. Stacklets
  204. ~~~~~~~~~
  205. Continulets are internally implemented using stacklets, which is the
  206. generic RPython-level building block for "one-shot continuations". For
  207. more information about them please see the documentation in the C source
  208. at :source:`rpython/translator/c/src/stacklet/stacklet.h`.
  209. The module ``rpython.rlib.rstacklet`` is a thin wrapper around the above
  210. functions. The key point is that new() and switch() always return a
  211. fresh stacklet handle (or an empty one), and switch() additionally
  212. consumes one. It makes no sense to have code in which the returned
  213. handle is ignored, or used more than once. Note that ``stacklet.c`` is
  214. written assuming that the user knows that, and so no additional checking
  215. occurs; this can easily lead to obscure crashes if you don't use a
  216. wrapper like PyPy's '_continuation' module.
  217. Theory of composability
  218. ~~~~~~~~~~~~~~~~~~~~~~~
  219. Although the concept of coroutines is far from new, they have not been
  220. generally integrated into mainstream languages, or only in limited form
  221. (like generators in Python and iterators in C#). We can argue that a
  222. possible reason for that is that they do not scale well when a program's
  223. complexity increases: they look attractive in small examples, but the
  224. models that require explicit switching, for example by naming the target
  225. coroutine, do not compose naturally. This means that a program that
  226. uses coroutines for two unrelated purposes may run into conflicts caused
  227. by unexpected interactions.
  228. To illustrate the problem, consider the following example (simplified
  229. code using a theorical ``coroutine`` class). First, a simple usage of
  230. coroutine::
  231. main_coro = coroutine.getcurrent() # the main (outer) coroutine
  232. data = []
  233. def data_producer():
  234. for i in range(10):
  235. # add some numbers to the list 'data' ...
  236. data.append(i)
  237. data.append(i * 5)
  238. data.append(i * 25)
  239. # and then switch back to main to continue processing
  240. main_coro.switch()
  241. producer_coro = coroutine()
  242. producer_coro.bind(data_producer)
  243. def grab_next_value():
  244. if not data:
  245. # put some more numbers in the 'data' list if needed
  246. producer_coro.switch()
  247. # then grab the next value from the list
  248. return data.pop(0)
  249. Every call to grab_next_value() returns a single value, but if necessary
  250. it switches into the producer function (and back) to give it a chance to
  251. put some more numbers in it.
  252. Now consider a simple reimplementation of Python's generators in term of
  253. coroutines::
  254. def generator(f):
  255. """Wrap a function 'f' so that it behaves like a generator."""
  256. def wrappedfunc(*args, **kwds):
  257. g = generator_iterator()
  258. g.bind(f, *args, **kwds)
  259. return g
  260. return wrappedfunc
  261. class generator_iterator(coroutine):
  262. def __iter__(self):
  263. return self
  264. def next(self):
  265. self.caller = coroutine.getcurrent()
  266. self.switch()
  267. return self.answer
  268. def Yield(value):
  269. """Yield the value from the current generator."""
  270. g = coroutine.getcurrent()
  271. g.answer = value
  272. g.caller.switch()
  273. def squares(n):
  274. """Demo generator, producing square numbers."""
  275. for i in range(n):
  276. Yield(i * i)
  277. squares = generator(squares)
  278. for x in squares(5):
  279. print x # this prints 0, 1, 4, 9, 16
  280. Both these examples are attractively elegant. However, they cannot be
  281. composed. If we try to write the following generator::
  282. def grab_values(n):
  283. for i in range(n):
  284. Yield(grab_next_value())
  285. grab_values = generator(grab_values)
  286. then the program does not behave as expected. The reason is the
  287. following. The generator coroutine that executes ``grab_values()``
  288. calls ``grab_next_value()``, which may switch to the ``producer_coro``
  289. coroutine. This works so far, but the switching back from
  290. ``data_producer()`` to ``main_coro`` lands in the wrong coroutine: it
  291. resumes execution in the main coroutine, which is not the one from which
  292. it comes. We expect ``data_producer()`` to switch back to the
  293. ``grab_next_values()`` call, but the latter lives in the generator
  294. coroutine ``g`` created in ``wrappedfunc``, which is totally unknown to
  295. the ``data_producer()`` code. Instead, we really switch back to the
  296. main coroutine, which confuses the ``generator_iterator.next()`` method
  297. (it gets resumed, but not as a result of a call to ``Yield()``).
  298. Thus the notion of coroutine is *not composable*. By opposition, the
  299. primitive notion of continulets is composable: if you build two
  300. different interfaces on top of it, or have a program that uses twice the
  301. same interface in two parts, then assuming that both parts independently
  302. work, the composition of the two parts still works.
  303. A full proof of that claim would require careful definitions, but let us
  304. just claim that this fact is true because of the following observation:
  305. the API of continulets is such that, when doing a ``switch()``, it
  306. requires the program to have some continulet to explicitly operate on.
  307. It shuffles the current continuation with the continuation stored in
  308. that continulet, but has no effect outside. So if a part of a program
  309. has a continulet object, and does not expose it as a global, then the
  310. rest of the program cannot accidentally influence the continuation
  311. stored in that continulet object.
  312. In other words, if we regard the continulet object as being essentially
  313. a modifiable ``f_back``, then it is just a link between the frame of
  314. ``callable()`` and the parent frame --- and it cannot be arbitrarily
  315. changed by unrelated code, as long as they don't explicitly manipulate
  316. the continulet object. Typically, both the frame of ``callable()``
  317. (commonly a local function) and its parent frame (which is the frame
  318. that switched to it) belong to the same class or module; so from that
  319. point of view the continulet is a purely local link between two local
  320. frames. It doesn't make sense to have a concept that allows this link
  321. to be manipulated from outside.