Easy async?

462 views
Skip to first unread message

JohnMudd

unread,
Nov 15, 2010, 1:53:03 PM11/15/10
to Tornado Web Server
In this example I pause if a user fails to login properly. For demo
purposes, I'm just testing if the user=='johnmudd'. If not, I sleep
for 4 secs before rejecting the login.

I set up a general thread pool to handle calling the deferred version
of methods. This seems to work well. Is this all I need or are there
cases where this approach won't work? For example, can I use this
approach and call urllib directly from my deferred method instead of
using tornado.httpclient.AsyncHTTPClient?


threadLimit = 10
taskQueue = Queue.Queue(threadLimit*2)

class TaskProcessor(threading.Thread, BaseHandler):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def run(self):
while True:
taskQueue.get()() # call the next queued async method.

for i in range(threadLimit):
t = TaskProcessor()


class LoginHandler(BaseHandler):
def get(self):
...

@tornado.web.asynchronous
def post(self):
taskQueue.put(self.async_callback(self.post_deferred))
def post_deferred(self):
user = self.get_argument('username')
if user == 'johnmudd':
self.set_secure_cookie('user',
self.get_argument('username'))
nextUrl = self.get_secure_cookie('nextUrl')
self.clear_cookie('nextUrl')
if nextUrl == None: nextUrl = '/'
self.redirect(nextUrl) # Calls finish().
else:
time.sleep(4)
raise tornado.web.HTTPError(401, "Auth failed")

Ben Darnell

unread,
Nov 15, 2010, 2:22:48 PM11/15/10
to python-...@googlegroups.com
Mixing Tornado and threads is possible but tricky. Many tornado
methods can only be called safely from the IOLoop's thread. In
general, the worker threads should not touch anything on the
RequestHandler. Instead, when the work is done, they should call
IOLoop.add_callback() to transfer control back to the IOLoop so the
RequestHandler can produce its output.

-Ben

Message has been deleted

JohnMudd

unread,
Nov 16, 2010, 12:11:41 PM11/16/10
to Tornado Web Server
Thanks! You saved me from a dumb assumption that Tornado methods are
thread safe.

I looked at the code for add_callback(). I see that all it does is
add an item to a set. Sets are thread safe in Python so it makes
sense that add_callback() is thread safe. This is the only method
I'll use from my threads.

Here's my new approach. In this case post() sets up a call to
post_thread() and then post_thread() sets up a call to
post_results().

post() --> post_thread() --> post_results()

The post() and post_results() methods are executed in the main
"thread" under control of IOLoop. These two can call Tornado methods
safely. The post_thread() method is run inside one of the threads
from my thread pool. This is where I can run code with long delays
(e.g. sleep()) without blocking other connections. The only Tornado
methods I will call from post_thread() are add_callback() and
async_callback(). These two methods are thread safe.

This is just one example but demonstrates a general pattern how I can
safely mix threading with Tornado. Right?


@tornado.web.asynchronous
def post(self):
self.user = self.get_argument('username')
taskQueue.put(self.post_thread)
def post_thread(self):
if self.user == 'johnmudd':
# Successful login, nothing else to do for now.
pass
else:
time.sleep(4)
self.user = None

tornado.ioloop.IOLoop.instance().add_callback(self.async_callback(self.post_results))
def post_results(self):
if self.user:
self.set_secure_cookie('user', self.user)
nextUrl = self.get_secure_cookie('nextUrl')
self.clear_cookie('nextUrl')
if nextUrl == None: nextUrl = '/'
self.redirect(nextUrl) # Calls finish().
else:
raise tornado.web.HTTPError(401, "Auth failed")



Ben Darnell

unread,
Nov 16, 2010, 2:40:29 PM11/16/10
to python-...@googlegroups.com
Yes, that's the right pattern for doing blocking work in a thread in a
tornado handler.

-Ben

JohnMudd

unread,
Dec 1, 2010, 9:42:21 AM12/1/10
to Tornado Web Server
I'm curious... can you point out some RequestHandler methods that can
*not* be called safely outside the IOLoop's thread? I'm looking at
the class and the only clearly non-thread safe method I see is
_execute() because it uses stack_context.StackContext() which makes
use of the global _state variable in stack_context.py. Any other non-
thread safe methods?

John

On Nov 15, 2:22 pm, Ben Darnell <b...@bendarnell.com> wrote:
> Mixing Tornado and threads is possible but tricky.  Many tornado
> methods can only be called safely from the IOLoop's thread.  In
> general, the worker threads should not touch anything on the
> RequestHandler.  Instead, when the work is done, they should call
> IOLoop.add_callback() to transfer control back to the IOLoop so the
> RequestHandler can produce its output.
>
> -Ben
>

Ben Darnell

unread,
Dec 1, 2010, 1:12:46 PM12/1/10
to python-...@googlegroups.com
_execute and StackContext are actually thread-safe
(stack_context._state is a threading.Local). The thread-unsafe
methods are anything that touches the IOStream, i.e. flush() and
finish() (which are called from a lot of other places, including error
handlers).

-Ben

Reply all
Reply to author
Forward
0 new messages