On Sat, Dec 15, 2012 at 5:36 PM, Jason Tackaberry <span dir="ltr"><<a href="mailto:tack@urandom.ca" target="_blank">tack@urandom.ca</a>></span> wrote:<br><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div text="#000000" bgcolor="#FFFFFF">With Kaa, our future-style object is called an InProgress (so
forgive the differing terminology in the remainder of this post):<br>
<br>
<a href="http://api.freevo.org/kaa-base/async/inprogress.html" target="_blank">http://api.freevo.org/kaa-base/async/inprogress.html</a><br>
<br>
A couple properties of InProgress objects that I've found have
practical value:<br>
<ul>
<li>they can be aborted, which raises a special InProgressAborted
inside the coroutine function so it can perform cleanup actions</li>
<ul>
<li>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.)</li>
<li>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.)<br>
</li>
<li>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.)</li>
<li>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</li>
<li>discussion in the PEP on cancellation has some TBDs so
perhaps the above will be food for thought<br></li></ul></ul></div></blockquote><div>The PEP is definitely weak. Here are some thoughts/proposals though:</div><div><ul><li>You can't cancel a coroutine; however you can cancel a Task, which is a Future wrapping a stack of coroutines linked via yield-from.</li>
<li>Cancellation only takes effect when a task is suspended.</li><li>When you cancel a Task, the most deeply nested coroutine (the one that caused it to be suspended) receives a special exception (I propose to reuse concurrent.futures.CancelledError from PEP 3148). If it doesn't catch this it bubbles all the way to the Task, and then out from there.</li>
<li>However when a coroutine in one Task uses yield-from to wait for another Task, the latter does not automatically get cancelled. So this is a difference between "yield from foo()" and "yield from Task(foo())", which otherwise behave pretty similarly. Of course the first Task could catch the exception and cancel the second task -- that is its responsibility though and not the default behavior.</li>
<li>PEP 3156 has a par() helper which lets you block for multiple tasks/coroutines in parallel. It takes arguments which are either coroutines, Tasks, or other Futures; it wraps the coroutines in Tasks to run them independently an just waits for the other arguments. Proposal: when the Task containing the par() call is cancelled, the par() call intercepts the cancellation and by default cancels those coroutines that were passed in "bare" but not the arguments that were passed in as Tasks or Futures. Some keyword argument to par() may be used to change this behavior to "cancel none" or "cancel all" (exact API spec TBD).</li>
</ul><div><br></div></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div text="#000000" bgcolor="#FFFFFF"><ul><li>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</li>
<ul>
<li>it's noteworthy that timeout() returns a <i>new</i>
InProgress and the original task continues on even if the
timeout occurs -- by default that is, unless you do
timeout(abort=True)<br>
</li>
<li>I didn't see much discussion in the PEP on timeouts, but I
think this is an important feature that should be standardized<br></li></ul></ul></div></blockquote><div>Interesting. In Tulip v1 (the experimental version I wrote before PEP 3156) the Task() constructor has an optional timeout argument. It works by scheduling a callback at the given time in the future, and the callback simply cancel the task (which is a no-op if the task has already completed). It works okay, except it generates tracebacks that are sometimes logged and sometimes not properly caught -- though some of that may be my messy test code. The exception raised by a timeout is the same CancelledError, which is somewhat confusing. I wonder if Task.cancel() shouldn't take an exception with which to cancel the task with. (TimeoutError in PEP 3148 has a different role, it is when the timeout on a specific wait expires, so e.g. fut.result(timeout=2) waits up to 2 seconds for fut to complete, and if not, the call raises TimeoutError, but the code running in the executor is unaffected.)</div>
<div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div text="#000000" bgcolor="#FFFFFF">
<br>
Coroutines in Kaa use "yield" rather than "yield from" but the
general approach looks very similar to what's been proposed:<br>
<br>
<a href="http://api.freevo.org/kaa-base/async/coroutines.html" target="_blank">http://api.freevo.org/kaa-base/async/coroutines.html</a><br>
<br>
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.<br></div></blockquote><div><br></div><div>We've had long discussions about yield vs. yield-from. The latter is way more efficient and that's enough for me to push it through. When using yield, each yield causes you to bounce to the scheduler, which has to do a lot of work to decide what to do next, even if that is just resuming the suspended generator; and the scheduler is responsible for keeping track of the stack of generators. When using yield-from, calling another coroutine as a subroutine is almost free and doesn't involve the scheduler at all; thus it's much cheaper, and the scheduler can be simpler (doesn't need to keep track of the stack). Also stack traces and debugging are better.</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div text="#000000" bgcolor="#FFFFFF">
<br>
There are some features of Kaa's implementation that could be worth
considering:<br>
<ul>
<li>it is possible to yield a special object (called NotFinished)
that allows a coroutine to "time slice" as a form of cooperative
multitasking</li></ul></div></blockquote><div>I can recommend yield from tulip.sleep(0) for that.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div text="#000000" bgcolor="#FFFFFF"><ul>
<li>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</li>
<li>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</li></ul></div></blockquote><div><br></div><div>These seem pretty esoteric and can probably implemented in user code if needed.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div text="#000000" bgcolor="#FFFFFF"><ul>
</ul>
<br>
Once you've standardized on a way to manage the lifecycle of an
in-progress asynchronous task, threads are a natural extension:<br>
<br>
<a href="http://api.freevo.org/kaa-base/async/threads.html" target="_blank">http://api.freevo.org/kaa-base/async/threads.html</a><br>
<br>
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.<br></div></blockquote><div><br></div><div>As I said, I think wait_for_future() and run_in_executor() in the PEP give you all you need. The @threaded decorator you propose is just sugar; if a user wants to take an existing API and convert it from a coroutine to threaded without requiring changes to the caller, they can just introduce a helper that is run in a thread with run_in_executor().</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div text="#000000" bgcolor="#FFFFFF">
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.<br></div></blockquote><div><br></div><div>Thanks for your very useful contribution! Kaa looks like an interesting system. Is it ported to Python 3 yet? Maybe you could look into integrating with the PEP 3156 event loop and/or scheduler.</div>
</div><div><br></div>-- <br>--Guido van Rossum (<a href="http://python.org/~guido">python.org/~guido</a>)<br>