summaryrefslogtreecommitdiff
path: root/docs/switching.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/switching.rst')
-rw-r--r--docs/switching.rst168
1 files changed, 168 insertions, 0 deletions
diff --git a/docs/switching.rst b/docs/switching.rst
new file mode 100644
index 0000000..b1de9b8
--- /dev/null
+++ b/docs/switching.rst
@@ -0,0 +1,168 @@
+.. _switching:
+
+==========================================================
+ Switching Between Greenlets: Passing Objects and Control
+==========================================================
+
+.. This is an "explanation" document.
+
+.. currentmodule:: greenlet
+
+Switches between greenlets occur when:
+
+- The method `greenlet.switch` of a greenlet is
+ called, in which case execution jumps to the greenlet whose ``switch()`` is
+ called; or
+- When the method `greenlet.throw` is used to raise an exception in
+ the target greenlet, in which case execution jumps to the greenlet
+ whose ``throw`` was called; or
+- When a greenlet dies, in which case execution jumps to the
+ parent greenlet.
+
+During a switch, an object or an exception is "sent" to the target
+greenlet; this can be used as a convenient way to pass information
+between greenlets. For example:
+
+.. doctest::
+
+ >>> from greenlet import greenlet
+ >>> def test1(x, y):
+ ... z = gr2.switch(x + y)
+ ... print(z)
+
+ >>> def test2(u):
+ ... print(u)
+ ... gr1.switch(42)
+
+ >>> gr1 = greenlet(test1)
+ >>> gr2 = greenlet(test2)
+ >>> gr1.switch("hello", " world")
+ hello world
+ 42
+
+This prints "hello world" and 42. Note that the arguments of
+``test1()`` and ``test2()`` are not provided when the greenlet is
+created, but only the first time someone switches to it.
+
+Here are the precise rules for sending objects around:
+
+``g.switch(*args, **kwargs)``
+ Switches execution to the greenlet ``g``, sending it the given
+ arguments. As a special case, if ``g`` did not start yet, then it
+ will start to run now; ``args`` and ``kwargs`` are passed to the
+ greenlet's ``run()`` function as its arguments.
+
+Dying greenlet
+ If a greenlet's ``run()`` finishes, its return value is the object
+ sent to its parent. If ``run()`` terminates with an exception, the
+ exception is propagated to its parent (unless it is a
+ ``greenlet.GreenletExit`` exception, in which case the exception
+ object is caught and *returned* to the parent).
+
+Apart from the cases described above, the target greenlet normally
+receives the object as the return value of the call to ``switch()`` in
+which it was previously suspended. Indeed, although a call to
+``switch()`` does not return immediately, it will still return at some
+point in the future, when some other greenlet switches back. When this
+occurs, then execution resumes just after the ``switch()`` where it was
+suspended, and the ``switch()`` itself appears to return the object that
+was just sent. This means that ``x = g.switch(y)`` will send the object
+``y`` to ``g``, and will later put the (unrelated) object that some
+(unrelated) greenlet passes back to us into ``x``.
+
+Multiple And Keyword Arguments
+==============================
+
+You can pass multiple or keyword arguments to ``switch()``. If the
+greenlet hasn't begun running, those are passed as function arguments
+to ``run`` as usual in Python. If the greenlet *was* running, multiple
+arguments will be a :class:`tuple`, and keyword arguments will be a
+:class:`dict`; any number of positional arguments with keyword
+arguments will have the entire set in a tuple, with positional
+arguments in their own nested tuple, and keyword arguments as a `dict`
+in the the last element of the tuple:
+
+.. doctest::
+
+ >>> def test1(x, y, **kwargs):
+ ... while 1:
+ ... z = gr2.switch(x + y + ' ' + str(kwargs))
+ ... if not z: break
+ ... print(z)
+
+ >>> def test2(u):
+ ... print(u)
+ ... # A single argument -> itself
+ ... gr1.switch(42)
+ ... # Multiple positional args -> a tuple
+ ... gr1.switch("how", "are", "you")
+ ... # Only keyword arguments -> a dict
+ ... gr1.switch(language='en')
+ ... # one positional and keywords -> ((tuple,), dict)
+ ... gr1.switch("howdy", language='en_US')
+ ... # multiple positional and keywords -> ((tuple,), dict)
+ ... gr1.switch("all", "y'all", language='en_US_OK')
+ ... gr1.switch(None) # terminate
+
+ >>> gr1 = greenlet(test1)
+ >>> gr2 = greenlet(test2)
+ >>> gr1.switch("hello", " world", language='en')
+ hello world {'language': 'en'}
+ 42
+ ('how', 'are', 'you')
+ {'language': 'en'}
+ (('howdy',), {'language': 'en_US'})
+ (('all', "y'all"), {'language': 'en_US_OK'})
+
+.. _switch_to_dead:
+
+Switching To Dead Greenlets
+===========================
+
+Note that any attempt to switch to a dead greenlet actually goes to the
+dead greenlet's parent, or its parent's parent, and so on. (The final
+parent is the "main" greenlet, which is never dead.)
+
+
+.. doctest::
+
+ >>> def inner():
+ ... print("Entering inner.")
+ ... print("Returning from inner.")
+ ... return 42
+ >>> def outer():
+ ... print("Entering outer and spawning inner.")
+ ... inner_glet = greenlet(inner)
+ ... print("Switching to inner.")
+ ... result = inner_glet.switch()
+ ... print("Got from inner value: %s" % (result,))
+ ... print("Switching to inner again.")
+ ... result = inner_glet.switch()
+ ... print("Got from inner value: %s" % (result,))
+ ... return inner_glet
+ >>> outer_glet = greenlet(outer)
+
+Here, our main greenlet has created another greenlet (``outer``), which in turn
+creates a greenlet (``inner``). The outer greenlet switches to the
+inner greenlet, which immediately finishes and dies; the outer greenlet
+attempts to switch back to the inner greenlet, but since the inner
+greenlet is dead, it just switches...to itself (since it was the
+parent). Note how the second switch (to the dead greenlet) returns an
+empty tuple.
+
+.. doctest::
+
+ >>> inner_glet = outer_glet.switch()
+ Entering outer and spawning inner.
+ Switching to inner.
+ Entering inner.
+ Returning from inner.
+ Got from inner value: 42
+ Switching to inner again.
+ Got from inner value: ()
+
+We can similarly ask the main greenlet to switch to the (dead) inner
+greenlet and its (dead) parent and wind up still in the main greenlet.
+
+ >>> inner_glet.switch()
+ ()