Re: [python-tulip] aiohttp and classical web frameworks
On Tue, Feb 18, 2014 at 02:24:39PM -0500, Yury Selivanov wrote: On 2/18/2014, 4:44 AM, chrysn wrote: technically, i added an `asyncio.events.current_context` global dictionary, [...] Don't forget to patch Future.add_done_callback() too. thanks for the tip, but i think with the new approach of using current_task, this is obsolete. chrysn signature.asc Description: Digital signature
Re: [python-tulip] aiohttp and classical web frameworks
On Fri, Feb 21, 2014 at 7:01 AM, chrysn chr...@fsfe.org wrote: On Tue, Feb 18, 2014 at 11:48:19AM -0700, Don Brown wrote: snip / it does; you'd have to add a register_subtask(the_new_task) function, which stores the new task's id in a dictionary that get_ident conults to tell werkzeug that we're in the original task. I wonder how that would affect third-party libraries. I guess it should only affect those that either use request or call functions that use request, but still I worry it'd be a source of obscure bugs. As for the patch, give me your github account and I'll give you commit access. my github user is chrysn. Added. [...] though you do have to be careful for things like SQLAlchemy that will block for everything. i've read contradictory statements on whether sqlalchemy will have an asyncio interface or not. for the time being, some of that might have to be run in an executor (or on a server that has multiple threads, and is ok with some blocking). Yeah, that's the approach the twisted adbapi lib takes (create a bunch of threads), which is about the only thing you can do really. It does make me wonder if we wouldn't be better off just forking or creating a new web framework to make it more clear you do need async versions of everything, kinda like Node has effectively done by using server-side Javascript. the web frameworks should be portable enough (as you have demonstrated with flask and bottle), and i don't think we want to start rewriting sqlalchemy from scratch. Well, even with those, I had to make an incompatible fork that won't be accepted for a long time, as they want to preserve 2.x compatibility. I agree writing things from scratch is rather perilous as I'm confirming with my experiences with the redis and mongo libraries :) snip / an issue i have not solved to my satisfaction is sending early headers. in flask, unless i resort to writing a full wsgi application (which i currently do), heades are sent when a Response object is returned. that works well for things like get-request/asyncio-something/return-data workflows, but not for get-request/return-heades/wait/send-data/... workflows as they'd seen in longpolling, server-sent events, animated-gif streaming etc. Oh, and there is the issue about reading the request body. aiohttp supports reading as a coroutine where flask and others assume it is a blocking stream. How aiohttp supports that is by reading the whole body into a byte stream then returning that. While that is fine for most web apps, it isn't ideal. moreover, this should integrate somehow with pep, which uses yield as a means of flow control. do you already have a solution for that at hand? Nope, haven't looked at that yet. I've been running these with a gunicorn + aiohttp (note you have to fix the aiohttp worker to read the request body fully first). Don best regards chrysn -- To use raw power is to make yourself infinitely vulnerable to greater powers. -- Bene Gesserit axiom
Re: [python-tulip] aiohttp and classical web frameworks
hello don, On Mon, Feb 17, 2014 at 04:42:52PM -0700, Don Brown wrote: I'm wondering how possible this really is. Flask and Bottle, at least, depend on setting a thread-local variable to store the current request, response, and general environment, but with asyncio allowing multiple concurrent requests on a single process/thread, those variables start getting stepped. In particular, my Flask fork is having issues as Flask keeps complaining the request is incorrect (Popped wrong request context.), but given the nature of concurrent requests, it isn't 100% consistent. Therefore, I wonder if the current popular web frameworks can be used at all, and instead, we'd have to rewrite one or more to not depend on thread-local variables, among other things. flask (and other werkzeug-based frameworks) seem not to use thread-local variables directly, but have their own local manager[1]. especially, the flask RequestContext objects (which fling the described exceptions) use that mechanism. using your hello.py example, i'll try whether it is sufficient to adapt the local module to not be thread-local, but request-local. (not sure where to hook it up exactly, might need dirty stuff like stack inspection for a first hack). i'm very new to flask (used django and pylons ages ago, and just recently needed web stuff again), but it is too nice not to try to use it with asyncio :-) best regards chrysn [1] http://werkzeug.pocoo.org/docs/local/ -- To use raw power is to make yourself infinitely vulnerable to greater powers. -- Bene Gesserit axiom signature.asc Description: Digital signature
Re: [python-tulip] aiohttp and classical web frameworks
On Tue, Feb 18, 2014 at 09:21:52AM +0100, chrysn wrote: flask (and other werkzeug-based frameworks) seem not to use thread-local variables directly, but have their own local manager[1]. especially, the flask RequestContext objects (which fling the described exceptions) use that mechanism. i've got a version running that can deal with simultaneous connections and allows access to the flask/werkzeug request object in the way werkzeug expects it (see patches against python3.4 asyncio and don brown's flask+werkzeug branch). the good thing is that on werkzeug's part, the changes are minimal -- just implement an appropriate get_ident() function. the bad thing is that so far, i have to modify the event loop for this to work. in sloppy words: werkzeug demands a concept of task identity (tell me something that is unique while we work off this request). asyncio has, to the best of my knowledge, no such concept -- it's working on everything at the same time, and if you want to pass context, you pretty damn please pass around the respective objects. (you don't expect me to have stdout track your so-called 'currently active connection' either, do you?). now to reconcile that, i added a patch to asyncio along the lines of if you insist, i'll pass a note down to all future actions you trigger from inside this request. technically, i added an `asyncio.events.current_context` global dictionary, made all `asyncio.events.Handle` objects copy that on creation, and made the base event loop set the context dictionary of the handle to be executed globally, both serving as a template for newly created handles and allowing the currently executing task to modify that dictionary. my werkzeug get_ident function now places an object in `current_context['werkzeug_ident']` if there is none, and uses its id as a handle. this approach has several shortcomings: * it does not work with out-of-the-box asyncio (and presumably violates its design) * the current_context variable is not thread-safe. if one wants to execute several asyncio worker threads (does that work?), it could be made thread-local. * client-side hangups don't get the right id, causing exceptions and probably memory leakage. presumably, this is because the werkzeug identity gets assigned by the time the request is parsed, when the on-connection-closed hook has already been established. calling get_ident right after accept()ing might solve that. i'm clueless as to whether this can be solved purely werkzeug-side (short of passing identity/context around explicitly everywhere to functions that want to call get_ident; i'd call that the proper solution, but it looks like a design decision on werkzeug's part). even with heavily patching werkzeug, i don't see how that context information can be passed on, say, from one task waiting for a timeout, to another future, somewhere else, finally ending up in a werkzeug function again, without help from asyncio. what are your opinions? can this be made to work with out-of-the-box asyncio? best regards chrysn -- To use raw power is to make yourself infinitely vulnerable to greater powers. -- Bene Gesserit axiom diff --git a/werkzeug/local.py b/werkzeug/local.py index 47a9264..cbeffaa 100644 --- a/werkzeug/local.py +++ b/werkzeug/local.py @@ -23,6 +23,14 @@ except ImportError: except ImportError: from _thread import get_ident +def get_ident(): +import asyncio.events +if 'werkzeug_ident' not in asyncio.events.current_context: +print('creating a new identity') +asyncio.events.current_context['werkzeug_ident'] = object() +context = id(asyncio.events.current_context['werkzeug_ident']) +print(getident called; the context i have is %x%context) +return context def release_local(local): Releases the contents of the local for the current context. diff -ru /usr/lib/python3.4/asyncio/base_events.py asyncio/base_events.py --- /usr/lib/python3.4/asyncio/base_events.py 2014-02-11 16:19:29.0 +0100 +++ asyncio/base_events.py 2014-02-18 10:00:50.495539486 +0100 @@ -661,5 +661,6 @@ for i in range(ntodo): handle = self._ready.popleft() if not handle._cancelled: +events.current_context = handle._context handle._run() handle = None # Needed to break cycles when an exception occurs. diff -ru /usr/lib/python3.4/asyncio/events.py asyncio/events.py --- /usr/lib/python3.4/asyncio/events.py 2014-02-11 16:19:29.0 +0100 +++ asyncio/events.py 2014-02-18 09:31:10.689543349 +0100 @@ -15,6 +15,7 @@ from .log import logger +current_context = {} class Handle: Object returned by callback registration methods. @@ -24,6 +25,7 @@ self._callback = callback self._args = args self._cancelled = False +self._context = current_context.copy() def __repr__(self): res = 'Handle({}, {})'.format(self._callback, self._args) signature.asc
Re: [python-tulip] aiohttp and classical web frameworks
Great work chrysn. Does the same limitation apply that you can't use Task manually any more? I use them a lot in a sql driver library I'm working on, and can see using them in other places as well. As for the patch, give me your github account and I'll give you commit access. I did send an email to the flask list and hopped on IRC, but didn't get a reply in either forum. The good news is Flask-Async can work with both sync and async Flask apps, though you do have to be careful for things like SQLAlchemy that will block for everything. It does make me wonder if we wouldn't be better off just forking or creating a new web framework to make it more clear you do need async versions of everything, kinda like Node has effectively done by using server-side Javascript. In the meantime, I've taken Bottle, forked it, and made it work for async apps. I did this by the usual sprinkling of yield from around, but also rewrote all uses of thread-local variables to be explicit. This means your handler method looks like this: @app.route('/') def home(request, response): ... It seems to my Python newbness mind that explicit parameters are more Pythonic as it matches how self is preferred to an implicit this. It certainly is more noisy, but not overly so. Anyway, my Bottle fork is here: https://github.com/mrdon/bottle It also should work with normal and coroutine handler methods. Bottle seems to have a much smaller set of extensions than Flask, which as I mentioned, could be considered a good thing here. Don On Tue, Feb 18, 2014 at 6:53 AM, chrysn chr...@fsfe.org wrote: hello don, On Tue, Feb 18, 2014 at 10:44:28AM +0100, chrysn wrote: [... much ...] had i read the python-tulip mailing list more carefully, i would have seen the ThreadLocal analogue thread. the whole hackery described in the abovementioned mail can be easily avoided by using `asyncio.Task.current_task()`. the attached new patch is much smaller, and i'd like to propose it for inclusion in your flask branch to solve the Popped wrong request context problem. compared to my last approach, it might cause troubles when users create a Task manually -- but then again, that only happens in asyncio aware applications, and they can pass on the request object explicitly again. i'll keep playing with your flask version, it feels pretty usable. (for the time being, even with sqlalchemy). afaict, your patch to flask is very small (just some additional yield-from, and some call_maybe_yield), and not even the werkzeug patch is too heavy. have you tried getting an opinion from the flask/werkzeug developers on inclusion yet, or an idea on how a version that can do both (classical and asyncio) can be maintained? best regards chrysn -- You don't use science to show that you're right, you use science to become right. -- Randall Munroe
Re: [python-tulip] aiohttp and classical web frameworks
On 2/18/2014, 4:44 AM, chrysn wrote: technically, i added an `asyncio.events.current_context` global dictionary, made all `asyncio.events.Handle` objects copy that on creation, and made the base event loop set the context dictionary of the handle to be executed globally, both serving as a template for newly created handles and allowing the currently executing task to modify that dictionary. Don't forget to patch Future.add_done_callback() too. Yury
Re: [python-tulip] aiohttp and classical web frameworks
Nice. I ended up forking Flask and Werkzeug to sprinkle yield from around as well as fix app.run: https://bitbucket.org/mrdon/flask I'm certainly not a fan of this approach but it is working and letting me develop sync or async web apps pretty easily. Don On Sun, Feb 16, 2014 at 7:06 AM, chrysn chr...@fsfe.org wrote: hello python-tulip list, i've played around with aiohttp and flask/werkzeug/pocoo (an wsgi based classical python web framework), and found that they can be made to play nicely with each other easily. some features of the werkzeug framework are sidestepped, i'm waiting for the developers' feedback to see how bad it is. the demo code can be fetched from [1], the discussion on pocoo-l...@googlegroups.com is archived at [2]. what are your opinions on this? if it works out, is there still a need for dedicated asyncio web frameworks, apart from the lowest layer that provides a wsgi+async interface? if the topic fits here, i'd like to keep this thread updated if relevant information comes back from the pocoo people. best regards, and thank you all for providing this cool new python base technology chrysn [1] https://www.gitorious.org/aiohttp-werkzeug-demo/aiohttp-werkzeug-demo/ [2] https://groups.google.com/forum/#!topic/pocoo-libs/mTS1X7hfnkk -- To use raw power is to make yourself infinitely vulnerable to greater powers. -- Bene Gesserit axiom