Right Way To "timeout" A Request In Tornado
Solution 1:
Tornado does not automatically close the request handler when the client drops the connection. However, you can override on_connection_close
to be alerted when the client drops, which would allow you to cancel the connection on your end. A context manager (or a decorator) could be used to handle setting a timeout for handling the request; use tornado.ioloop.IOLoop.add_timeout
to schedule some method that times out the request to run after timeout
as part of the __enter__
of the context manager, and then cancel that callback in the __exit__
block of the context manager. Here's an example demonstrating both of those ideas:
import time
import contextlib
from tornado.ioloop import IOLoop
import tornado.web
from tornado import gen
@gen.coroutinedefasync_sleep(timeout):
yield gen.Task(IOLoop.instance().add_timeout, time.time() + timeout)
@contextlib.contextmanagerdefauto_timeout(self, timeout=2): # Seconds
handle = IOLoop.instance().add_timeout(time.time() + timeout, self.timed_out)
try:
yield handle
except Exception as e:
print("Caught %s" % e)
finally:
IOLoop.instance().remove_timeout(handle)
ifnot self._timed_out:
self.finish()
else:
raise Exception("Request timed out") # Don't continue on passed this pointclassTimeoutableHandler(tornado.web.RequestHandler):
definitialize(self):
self._timed_out = Falsedeftimed_out(self):
self._timed_out = True
self.write("Request timed out!\n")
self.finish() # Connection to client closes here.# You might want to do other clean up here.classMainHandler(TimeoutableHandler):
@gen.coroutinedefget(self):
with auto_timeout(self): # We'll timeout after 2 seconds spent in this block.
self.sleeper = async_sleep(5)
yield self.sleeper
print("writing") # get will abort before we reach here if we timed out.
self.write("hey\n")
defon_connection_close(self):
# This isn't the greatest way to cancel a future, since it will not actually# stop the work being done asynchronously. You'll need to cancel that some# other way. Should be pretty straightforward with a DB connection (close# the cursor/connection, maybe?)
self.sleeper.set_exception(Exception("cancelled"))
application = tornado.web.Application([
(r"/test", MainHandler),
])
application.listen(8888)
IOLoop.instance().start()
Solution 2:
Another solution to this problem is to use gen.with_timeout:
import time
from tornado import gen
from tornado.util import TimeoutError
classMainHandler @gen.coroutinedefget(self):
try:
# I'm using gen.sleep here but you can use any future in this placeyield gen.with_timeout(time.time() + 2, gen.sleep(5))
self.write("This will never be reached!!")
except TimeoutError as te:
logger.warning(te.__repr__())
self.timed_out()
deftimed_out(self):
self.write("Request timed out!\n")
I liked the way handled by the contextlib solution but I'm was always getting logging leftovers.
The native coroutine solution would be:
asyncdefget(self):
try:
await gen.with_timeout(time.time() + 2, gen.sleep(5))
self.write("This will never be reached!!")
except TimeoutError as te:
logger.warning(te.__repr__())
self.timed_out()
Post a Comment for "Right Way To "timeout" A Request In Tornado"