Re: [Web-SIG] [PEP 444] Future- and Generator-Based Async Idea

2011-01-09 Thread Alice Bevan–McGregor
Here's what I've mutated Alex Grönholm's minimal middleware example 
into: (see the change history for the evolution of this)


https://gist.github.com/771398

A complete functional (as in function, not working ;) async-capable 
middleware layer (that does nothing) is 12 lines.  That, I think is a 
reasonable amount of boilerplate.  Also, no decorators needed.  It's 
quite readable, even the way I've compressed it.


The class-based version is basically identical, but with added comments 
explaining the assumptions this example makes and demonstrating where 
the acutal middleware code can be implemented for simple middleware.


- Alice.


___
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com


[Web-SIG] [PEP 444] Future- and Generator-Based Async Idea

2011-01-08 Thread Alice Bevan–McGregor
Warning: this assumes we're running on bizzaro-world PEP 444 that 
mandates applications are generators.  Please do not dismiss this idea 
out of hand but give it a good look and maybe some feedback.  ;)


--

Howdy!

I've finished touching up the p-code illustrating my idea of using 
generators to implement async functionality within a WSGI application 
and middleware, including the idea of a wsgi2ref-supplied decorator to 
simplify middleware.


https://gist.github.com/770743

There may be a few typos in there; I switched from the idea of passing 
back the returned value of the future to passing back the future itself 
in order to better handle exception handling (i.e. not requiring utter 
insanity in the middleware to determine the true source of an exception 
and the need to pass it along).


The second middleware demonstration (using a decorator) makes 
middleware look a lot more like an application: yielding futures, or a 
response, with the addition of yielding an application callable not 
explored in the first (long, but trivial) example.  I believe this 
should cover 99% of middleware use cases, including interactive 
debugging, request routing, etc. and the syntax isn't too bad, if you 
don't mind standardized decorators.


This should be implementable within the context of Marrow HTTPd 
(http://bit.ly/fLfamO) without too much difficulty.


As a side note, I'll be adding threading support to the server 
(actually, marrow.server, the underlying server/protocol abstraction 
m.s.http utilizes) using futures some time over the week-end by 
wrapping the async callback that calls the application with a call to 
an executor, making it immune to blocking, but I suspect the overhead 
will outweigh the benefit for speedy applications.


Testing multi-process vs. multi-threaded using 2 workers each and the 
prime calculation example, threading is 1.5x slower for CPU-intensive 
tasks under Python 2.7.  That's terrible.  It should be 2x; I have 2 
cores.  :/


- Alice.


___
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com


Re: [Web-SIG] [PEP 444] Future- and Generator-Based Async Idea

2011-01-08 Thread Alice Bevan–McGregor
As a quick note, this proposal would signifigantly benefit from the 
simplified syntax offered by PEP 380 (Syntax for Delegating to a 
Subgenerator) [1] and possibly PEP 3152 (Cofunctions) [2].  The former 
simplifies delegation and exception passing, and the latter simplifies 
the async side of this.


Unfortunately, AFIK, both are affected by PEP 3003 (Python Language 
Moratorium) [3], which kinda sucks.


- Alice.

[1] http://www.python.org/dev/peps/pep-0380/
[2] http://www.python.org/dev/peps/pep-3152/
[3] http://www.python.org/dev/peps/pep-3003/


___
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com


Re: [Web-SIG] [PEP 444] Future- and Generator-Based Async Idea

2011-01-08 Thread P.J. Eby

I made a few errors in that massive post...

At 12:00 PM 1/8/2011 -0500, P.J. Eby wrote:
My major concern about the approach is still that it requires a fair 
amount of overhead on the part of both app developers and middleware 
developers, even if that overhead mostly consists of importing and 
decorating.  (More below.)


The above turned out to be happily wrong by the end of the post, 
since no decorators or imports are actually required for app and 
middleware developers.




You can then implement response-processing middleware like this:

def latinize_body(body_iter):
while True:
chunk = yield body_iter
if chunk is None:
break
else:
yield piglatin(yield body_iter)


The last line above is incorrect; it should've been yield 
piglatin(chunk), i.e.:


def latinize_body(body_iter):
while True:
chunk = yield body_iter
if chunk is None:
break
else:
yield piglatin(chunk)

It's still rather unintuitive, though.  There are also plenty of 
topics left to discuss, both of the substantial and bikeshedding varieties.


One big open question still in my mind is, are these middleware 
idioms any easier to get right than the WSGI 1 ones?  For things that 
don't process response bodies, the answer seems to be yes: you just 
stick in a yield and you're done.


For things that DO process response bodies, however, you have to have 
ugly loops like the one above.


I suppose it could be argued that, as unintuitive as that 
body-processing loop is, it's still orders of magnitude more 
intuitive than a piece of WSGI 1 middleware that has to handle both 
application yields and write()s!


I suppose my hesitance is due to the fact that it's not as simple as:

return (piglatin(chunk) for chunk in body_iter)

Which is really the level of simplicity that I was looking 
for.  (IOW, all response-processing middleware pays in this 
slightly-added complexity to support the subset of apps and 
response-processing middleware that need to wait for events during 
body output.)


___
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com


Re: [Web-SIG] [PEP 444] Future- and Generator-Based Async Idea

2011-01-08 Thread P.J. Eby

At 05:39 AM 1/8/2011 -0800, Alice Bevan­McGregor wrote:
As a quick note, this proposal would signifigantly benefit from the 
simplified syntax offered by PEP 380 (Syntax for Delegating to a 
Subgenerator) [1] and possibly PEP 3152 (Cofunctions) [2].  The 
former simplifies delegation and exception passing, and the latter 
simplifies the async side of this.


Unfortunately, AFIK, both are affected by PEP 3003 (Python Language 
Moratorium) [3], which kinda sucks.


Luckily, neither PEP is necessary, since we do not need to support 
arbitrary protocols for the subgenerators being called.  This makes 
it possible to simply yield instead of yield from, and the 
trampoline functions take care of distinguishing a terminal 
(return) result from an intermediate one.


The Coroutine class I suggested, however, *does* accept explicit 
returns via raise StopIteration(value), so it is actually fully 
equivalent to supporting yield from, as long as it's used with an 
appropriate trampoline function.


(In fact, the structure of the Coroutine class I proposed was stolen 
from an earlier Python-Dev post I did in an attempt to show why PEP 
380 was unnecessary for doing coroutines.  ;-) )


In effect, the only thing that PEP 380 would add here is the syntax 
sugar for 'raise StopIteration(value)', but you can do that with:


def return_(value):
raise StopIteration(value)

In any case, my suggestion doesn't need this for either apps or 
response bodies, since the type of data yielded suffices to indicate 
whether the value is a return or not.  You only need a subgenerator 
to raise StopIteration if you want to return something to your caller 
that *isn't* a response or body chunk.


___
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com


Re: [Web-SIG] [PEP 444] Future- and Generator-Based Async Idea

2011-01-08 Thread Paul Davis
On Sat, Jan 8, 2011 at 6:26 AM, Alice Bevan–McGregor
al...@gothcandy.com wrote:
 Warning: this assumes we're running on bizzaro-world PEP 444 that mandates
 applications are generators.  Please do not dismiss this idea out of hand
 but give it a good look and maybe some feedback.  ;)

 --

 Howdy!

 I've finished touching up the p-code illustrating my idea of using
 generators to implement async functionality within a WSGI application and
 middleware, including the idea of a wsgi2ref-supplied decorator to simplify
 middleware.

        https://gist.github.com/770743

 There may be a few typos in there; I switched from the idea of passing back
 the returned value of the future to passing back the future itself in order
 to better handle exception handling (i.e. not requiring utter insanity in
 the middleware to determine the true source of an exception and the need to
 pass it along).

 The second middleware demonstration (using a decorator) makes middleware
 look a lot more like an application: yielding futures, or a response, with
 the addition of yielding an application callable not explored in the first
 (long, but trivial) example.  I believe this should cover 99% of middleware
 use cases, including interactive debugging, request routing, etc. and the
 syntax isn't too bad, if you don't mind standardized decorators.

 This should be implementable within the context of Marrow HTTPd
 (http://bit.ly/fLfamO) without too much difficulty.

 As a side note, I'll be adding threading support to the server (actually,
 marrow.server, the underlying server/protocol abstraction m.s.http utilizes)
 using futures some time over the week-end by wrapping the async callback
 that calls the application with a call to an executor, making it immune to
 blocking, but I suspect the overhead will outweigh the benefit for speedy
 applications.

 Testing multi-process vs. multi-threaded using 2 workers each and the prime
 calculation example, threading is 1.5x slower for CPU-intensive tasks under
 Python 2.7.  That's terrible.  It should be 2x; I have 2 cores.  :/

        - Alice.


 ___
 Web-SIG mailing list
 Web-SIG@python.org
 Web SIG: http://www.python.org/sigs/web-sig
 Unsubscribe:
 http://mail.python.org/mailman/options/web-sig/paul.joseph.davis%40gmail.com


For contrast, I thought it might be beneficial to have a comparison
with an implementation that didn't use async might look like:

http://friendpaste.com/4lFbZsTpPGA9N9niyOt9PF

If your implementation requires that people change source code (yield
vs return) when they move code between sync and async servers, doesn't
that pretty much violate the main WSGI goal of portability?

IMO, the async middleware is quite more complex than the current state
of things with start_response. The ability to subtly miss invoking the
generator, or invoking it too many times and dropping part of a
response. Forcing every middleware to unwrap iterators and handle
their own StopExceptions is worrisome as well.

I can't decide if casting the complexity of the async middleware as a
side benefit of discouraging authors was a joke or not.

Either way this proposal reminds me quite a bit of Duff's device [1].
On its own Duff's device is quite amusing and could even be employed
in some situations to great effect. On the other hand, any WSGI spec
has to be understandable and implementable by people from all skill
ranges. If its a spec that only a handful of people comprehend, then I
fear its adoption would be significantly slowed in practice.


[1] http://en.wikipedia.org/wiki/Duff's_device
___
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com


Re: [Web-SIG] [PEP 444] Future- and Generator-Based Async Idea

2011-01-08 Thread P.J. Eby

At 01:24 PM 1/8/2011 -0500, Paul Davis wrote:

For contrast, I thought it might be beneficial to have a comparison
with an implementation that didn't use async might look like:

http://friendpaste.com/4lFbZsTpPGA9N9niyOt9PF


Compare your version with this one, that uses my revision of Alice's proposal:

def my_awesome_application(environ):
# do stuff
yield b'200 OK', [], [Hello, World!]

def my_middleware(app):
def wrapper(environ):
# maybe edit environ
try:
status, headers, body = yield app(environ)
# maybe edit response:
# body = latinize(body)
yield status, headers, body
except:
# maybe handle error
finally:
# maybe release resources

def my_server(app, httpreq):
environ = wsgi.make_environ(httpreq)

def process_response(result):
status, headers, body = result
write_headers(httpreq, status, headers)
Coroutine(body, body_trampoline, finish_response)

def finish_response(result):
# cleanup, if any

Coroutine(app(environ), app_trampoline, process_response)


The primary differences are that the server needs to split some of 
its processing into separate routines, and response-processing done 
by middleware has to happen in a while loop rather than a for loop.




If your implementation requires that people change source code (yield
vs return) when they move code between sync and async servers, doesn't
that pretty much violate the main WSGI goal of portability?


The idea here would be to have WSGI 2 use this protocol exclusively, 
not to have two different protocols.




IMO, the async middleware is quite more complex than the current state
of things with start_response.


Under the above proposal, it isn't, since you can't (only) do a for 
loop over the response body; you have to write a loop and a 
push-based handler as well.  In this case, it is reduced to just 
writing one loop.


I'm still not entirely convinced of the viability of the approach, 
but I'm no longer in the that's just crazy talk category regarding 
an async WSGI.  The cost is no longer crazy, but there's still some 
cost involved, and the use case rationale hasn't improved much.


OTOH, I can now conceive of actually *using* such an async API for 
something, and that's no small feat.  Before now, the idea held 
virtually zero interest for me.




Either way this proposal reminds me quite a bit of Duff's device [1].
On its own Duff's device is quite amusing and could even be employed
in some situations to great effect. On the other hand, any WSGI spec
has to be understandable and implementable by people from all skill
ranges. If its a spec that only a handful of people comprehend, then I
fear its adoption would be significantly slowed in practice.


Under my modification of Alice's proposal, nearly all of the 
complexity involved migrates to the server, mostly in the (shareable) 
Coroutine implementation.


For an async server, the arrange for coroutine(result) to be called 
operations are generally native to async APIs, so I'd expect them to 
find that simple to implement.  Synchronous servers just need to 
invoke the waited-on operation synchronously, then pass the value 
back into the coroutine.  (e.g. by returning pause from the 
trampoline, then calling coroutine(value, exc_info) to resume 
processing after the result is obtained.)



___
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com


Re: [Web-SIG] [PEP 444] Future- and Generator-Based Async Idea

2011-01-08 Thread Graham Dumpleton
On 9 January 2011 12:16, Alice Bevan–McGregor al...@gothcandy.com wrote:
 On 2011-01-08 09:00:18 -0800, P.J. Eby said:

 (The next interesting challenge would be to integrate this withGraham's
 proposal for adding cleanup handlers...)

 class MyApplication(object):
   def __init__(self):
       pass # process startup code

   def __call__(self, environ):
       yield None # must be a generator
       pass # request code

   def __enter__(self):
       pass # request startup code

   def __exit(exc_type, exc_val, exc_tb):
       pass # request shutdown code -- regardless of exceptions

 We could mandate context managers!  :D  (Which means you can still wrap a
 simple function in @contextmanager.)

Context managers don't solve the problem I am trying to address. The
'with' statement doesn't apply context managers to WSGI application
objects in way that is desirable and use of a decorator to achieve the
same means having to replace close() which is what am trying to avoid
because of extra complexity that causes for WSGI middleware just to
make sure wsgi.file_wrapper works. We want a world where it should
never be necessary for WSGI middleware, or proxy decorators, to have
to fudge up a generator and override the close() chain to add
cleanups.

Graham

        - Alice.


 ___
 Web-SIG mailing list
 Web-SIG@python.org
 Web SIG: http://www.python.org/sigs/web-sig
 Unsubscribe:
 http://mail.python.org/mailman/options/web-sig/graham.dumpleton%40gmail.com

___
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com