[Python-ideas] Late to the async party (PEP 3156)

Jason Tackaberry tack at urandom.ca
Sun Dec 16 02:36:18 CET 2012


Hi python-ideas,

I've been somewhat living under a rock for the past few months and 
consequently I missed the ideal window of opportunity to weigh in on the 
async discussions this fall that culminated into PEP 3156.

I've been reading through those discussions in the archives.  I've not 
finished digesting it all, and I'm somewhat torn in that I feel I should 
shut up until I read everything to date so as not to decrease the SNR, 
but on the other hand, knowing myself, I strongly suspect this would 
result in my never speaking up.  And so, at risk of lowering the SNR ...

First let me say that PEP 3156 makes me very, very happy.

Over the past few years I've been exploring these very ideas with a 
little-used library called Kaa.  I'm not offering it up as a paragon of 
proper async library design, but I wanted to share some of my 
experiences in case they could be useful to the PEP.

     https://github.com/freevo/kaa-base/
     http://api.freevo.org/kaa-base/

It does seem like many similar design choices were made.  In particular, 
I'm happy that an explicit yield will be used rather than the greenlet 
style of implicit suspension/reentry.   Even after I've been using them 
for years, coroutines often feel like a form of magic, and an explicit 
yield is more aligned with the principle of least surprise.

With Kaa, our future-style object is called an InProgress (so forgive 
the differing terminology in the remainder of this post):

     http://api.freevo.org/kaa-base/async/inprogress.html

A couple properties of InProgress objects that I've found have practical 
value:

  * they can be aborted, which raises a special InProgressAborted inside
    the coroutine function so it can perform cleanup actions
      o what makes this tricky is the question of what to do to any
        currently yielded tasks?  If A yields B and A is aborted, should
        B be aborted?  What if the same B task is being yielded by C? 
        Should C also be aborted, even if it's considered a sibling of
        A?  (For example, suppose B is a task that is refreshing some
        common cache that both A and C want to make sure is up-to-date
        before they move on.)
      o if the decision is B should be aborted, then within A, 'yield B'
        will raise an exception because A is aborted, but 'yield B'
        within C will raise because B was aborted.  So there needs to be
        some mechanism to distinguish between these cases.  (My approach
        was to have an origin attribute on the exception.)
      o if A yields B, it may want to prevent B from being aborted if A
        is aborted.  (My approach was to have a noabort() method in
        InProgress objects to return a new, unabortable InProgress
        object that A can then yield.)
      o alternatively, the saner implementation may be to do nothing to
        B when A is aborted and require A catch InProgressAborted and
        explicitly abort B if that's the desired behaviour
      o discussion in the PEP on cancellation has some TBDs so perhaps
        the above will be food for thought
  * they have a timeout() method, which returns a new InProgress object
    representing the task that will abort when the timeout elapses if
    the task doesn't finish
      o it's noteworthy that timeout() returns a /new/ InProgress and
        the original task continues on even if the timeout occurs -- by
        default that is, unless you do timeout(abort=True)
      o I didn't see much discussion in the PEP on timeouts, but I think
        this is an important feature that should be standardized


Coroutines in Kaa use "yield" rather than "yield from" but the general 
approach looks very similar to what's been proposed:

     http://api.freevo.org/kaa-base/async/coroutines.html

The @coroutine decorator causes the decorated function to return an 
InProgress.  Coroutines can of course yield other coroutines, but, more 
fundamentally, anything else that returns an InProgress object, which 
could be a @threaded function, or even an ordinary function that 
explicitly creates and returns an InProgress object.

There are some features of Kaa's implementation that could be worth 
considering:

  * it is possible to yield a special object (called NotFinished) that
    allows a coroutine to "time slice" as a form of cooperative multitasking
  * coroutines can have certain policies that control invocation
    behaviour.  The most obvious ones to describe are
    POLICY_SYNCHRONIZED which ensures that multiple invocations of the
    same coroutine are serialized, and POLICY_SINGLETON which
    effectively ignores subsequent invocations if it's already running
  * it is possible to have a special progress object passed into the
    coroutine function so that the coroutine's progress can be
    communicated to an outside observer


Once you've standardized on a way to manage the lifecycle of an 
in-progress asynchronous task, threads are a natural extension:

     http://api.freevo.org/kaa-base/async/threads.html

The important element here is that @threaded decorated functions can be 
yielded by coroutines.  This means that truly blocking tasks can be 
wrapped in a thread but invocation from a coroutine is identical to any 
other coroutine.  Consequently, a threaded task could later be 
implemented as a coroutine (or more generally via event loop hooks) 
without any API changes.

I think I'll stop here.  There's plenty more definition, discussion, and 
examples in the links above.  Hopefully some ideas can be salvaged for 
PEP 3156, but even if that's not the case, I'll be happy to know they 
were considered and rejected rather than not considered at all.

Cheers,
Jason.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20121215/802f41fe/attachment.html>


More information about the Python-ideas mailing list