Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Allow prepare to be async #605

Closed
wants to merge 4 commits into from

3 participants

@acasajus

Allow prepare to be async like the normal get/post/... methods using the asynchronous decorator and having to call end_prepare.

@ei-grad

I think it would be good to mention this feature in docstring of asynchronous decorator.

ps. there was many requests for this, I also faced with need of asynchronous prepare to get a redis db connection from pool (or establish it if there are no free connections)... now i have to add r = redis.get_locked_connection() in the beginning of every handlers get/post methods... (of course, it could be implemented in custom BaseHandler, but it looks bad to change API semantics in the application this way)

@acasajus

Done :)

@bdarnell
Owner

Needs a test case. Tests should cover asynchronous prepare with both sync and async main methods, and also cover the case where the request is finished in prepare.

Maybe call the end method finish_prepare for consistency with finish? If I were starting from scratch I'd say that the \@asynchronous decorator would call the decorated method with an extra callback argument, but I guess it's not worth making the behavior of prepare and get/post/etc different.

@acasajus

Added the test case.

I was just trying to mimic the @asynchronous behavior for prepare. I don't think it's worth having two different ways of treating async stuff.

@acasajus

Is there more input wrt this or will it be merged?

@bdarnell
Owner

Sorry to leave this in the queue for so long. My thinking on asynchronous methods has evolved with the use of coroutines and Futures in 3.0. We can say now that a method is asynchronous if it returns a Future, and this means we don't have to give each potentially-asynchronous method its own finish method. The @asynchronous decorator already detects if its target returns a Future and runs finish() automatically (this is not just a convenience, but turns out to be necessary for error handling). I'd like to do something similar for prepare(), and potentially other methods.

@bdarnell bdarnell closed this pull request from a commit
@bdarnell bdarnell Allow prepare to be asynchronous, and detect coroutines by their result.
The prepare method does not use the @asynchronous decorator, only
@gen.coroutine (or @return_future; it detects the Future return type).
The same logic is now available for the regular http verb methods as well.

Closes #605.
ca8495d
@bdarnell bdarnell closed this in ca8495d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 4, 2012
  1. @acasajus

    Allow prepare to be async

    acasajus authored
  2. @acasajus
Commits on Oct 9, 2012
  1. @acasajus
Commits on Oct 10, 2012
  1. @acasajus

    Fixed test for python 3.2

    acasajus authored
This page is out of date. Refresh to see the latest.
Showing with 94 additions and 1 deletion.
  1. +65 −0 tornado/test/web_test.py
  2. +29 −1 tornado/web.py
View
65 tornado/test/web_test.py
@@ -953,3 +953,68 @@ def test_raise_with_reason(self):
def test_httperror_str(self):
self.assertEqual(str(HTTPError(682, reason="Foo")), "HTTP 682: Foo")
wsgi_safe.append(RaiseWithReasonTest)
+
+
+
+class BasePrepareTestHandler(RequestHandler):
+
+ def initialize( self, test ):
+ self.test = test
+
+ def on_finish(self):
+ self.test.on_finish()
+
+class AsyncPrepareAsyncHandler(BasePrepareTestHandler):
+
+ @asynchronous
+ def prepare( self ):
+ self.flush(callback=self.end_prepare)
+
+ @asynchronous
+ def get(self):
+ self.write( "hello" )
+ self.flush(callback=self.finish)
+
+class AsyncPrepareSyncHandler(BasePrepareTestHandler):
+
+ @asynchronous
+ def prepare( self ):
+ self.set_header( "X-Test", "hello" )
+ self.flush(callback=self.end_prepare)
+
+ def get(self):
+ self.write( "hello" )
+
+class SyncPrepareAsyncHandler(BasePrepareTestHandler):
+
+ @asynchronous
+ def get(self):
+ self.write( "hello" )
+ self.flush(callback=self.finish)
+
+class SyncPrepareSyncHandler(BasePrepareTestHandler):
+
+ def get(self):
+ self.write( "hello" )
+
+class AsyncPrepareTest(WebTestCase):
+
+ def get_handlers(self):
+ return [('/aa', AsyncPrepareAsyncHandler, dict(test=self)),
+ ('/as', AsyncPrepareSyncHandler, dict(test=self)),
+ ('/sa', SyncPrepareAsyncHandler, dict(test=self)),
+ ('/ss', SyncPrepareSyncHandler, dict(test=self))]
+
+ def test_async(self):
+ for url in ( '/aa', '/as', '/sa', '/ss' ):
+ logging.debug( "Trying connection to %s" % url )
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ s.connect(("localhost", self.get_http_port()))
+ self.stream = IOStream(s, io_loop=self.io_loop)
+ self.stream.write(b("GET %s HTTP/1.0\r\n\r\n" % url))
+ self.wait()
+
+ def on_finish(self):
+ logging.debug('connection closed')
+ self.stream.close()
+ self.stop()
View
30 tornado/web.py
@@ -112,6 +112,8 @@ def __init__(self, application, request, **kwargs):
self._headers_written = False
self._finished = False
self._auto_finish = True
+ self._prepared = False
+ self._auto_run = True
self._transforms = None # will be set in _execute
self.ui = ObjectDict((n, self._ui_method(m)) for n, m in
application.ui_methods.iteritems())
@@ -1056,7 +1058,17 @@ def _execute(self, transforms, *args, **kwargs):
if self.request.method not in ("GET", "HEAD", "OPTIONS") and \
self.application.settings.get("xsrf_cookies"):
self.check_xsrf_cookie()
+ self._method_args = ( args, kwargs )
self.prepare()
+ if self._auto_run:
+ self.end_prepare()
+ except Exception, e:
+ self._handle_request_exception(e)
+
+ def end_prepare( self ):
+ self._prepared = True
+ try:
+ args, kwargs = self._method_args
if not self._finished:
args = [self.decode_argument(arg) for arg in args]
kwargs = dict((k, self.decode_argument(v, name=k))
@@ -1151,12 +1163,28 @@ def _on_download(self, response):
self.write("Downloaded!")
self.finish()
+ This decorator also allows to execute the prepare method asynchronously.
+ The request handler has to call self.end_prepare() to continue the request
+ processing.
+
+ class MyRequestHandler(web.RequestHandler):
+ @web.asynchronous
+ def prepare(self):
+ doSomeStuff( "somevalue" , callback = self._on_done)
+
+ def _on_done(self):
+ #This is the end of prepare
+ self.end_prepare()
+
"""
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
if self.application._wsgi:
raise Exception("@asynchronous is not supported for WSGI apps")
- self._auto_finish = False
+ if not self._prepared:
+ self._auto_run = False
+ else:
+ self._auto_finish = False
with stack_context.ExceptionStackContext(
self._stack_context_handle_exception):
return method(self, *args, **kwargs)
Something went wrong with that request. Please try again.