Re: [python-tulip] aiohttp and classical web frameworks

2014-02-21 Thread chrysn
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

2014-02-21 Thread Don Brown
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

2014-02-18 Thread chrysn
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

2014-02-18 Thread chrysn
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

2014-02-18 Thread Don Brown
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

2014-02-18 Thread Yury Selivanov


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

2014-02-16 Thread Don Brown
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