Re: [Web-SIG] [PEP 444] Future- and Generator-Based Async Idea
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
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
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
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
At 05:39 AM 1/8/2011 -0800, Alice BevanMcGregor 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
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
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
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