Switching Between Greenlets: Passing Objects and Control

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:

>>> 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

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 tuple, and keyword arguments will be a 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:

>>> 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'}
('how', 'are', 'you')
{'language': 'en'}
(('howdy',), {'language': 'en_US'})
(('all', "y'all"), {'language': 'en_US_OK'})

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.)

>>> 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.

>>> 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()