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.000000000 +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.000000000 +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)

Attachment: signature.asc
Description: Digital signature

Reply via email to