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)
signature.asc
Description: Digital signature