OS: Windows 10
Tornado: 5.0
Python: 3.6.4
import tornado.web
import tornado.gen
import time
from tornado.ioloop import IOLoop
class TestHandler(tornado.web.RequestHandler):
def _call_later_something(self):
pass
def _get(self, var1):
# Does some time consuming call
# Mocking it
time.sleep(2)
IOLoop.current().call_later(10, self._call_later_something)
resp_dict = {}
resp_dict['blah'] = "blah"
return resp_dict
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self, *args, **kwargs):
var1 = self.get_argument('var1', None)
resp_dict = yield IOLoop.current().run_in_executor(None,
self._get,
var1)
self.write(resp_dict)
self.finish()
class MyApp(tornado.web.Application):
def __init__(self, test=False):
handlers = [
(r"/api/sometest", TestHandler),
]
tornado_settings = dict(
debug=True,
serve_traceback=True,
)
tornado.web.Application.__init__(self, handlers, **tornado_settings)
if __name__ == "__main__":
# Hard code this so that jarvis and ironman can be in sync
port_number = 9999
http_server = MyApp()
http_server.listen(port_number)
IOLoop.instance().start()
This works completely fine on Python2.7, throws the error
RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'
Not sure what I am doing wrong.
This is because of how asyncio works which is used by default in Tornado 5.0 on Python versions that support it. See #2183 for details.
@mivade Thanks!
Also note that while this may sometimes work on Python 2, it's not correct. IOLoop.current().call_later is not safe to call from other threads and it won't always do what you want here.
Do everything that interacts with the IOLoop in the function that calls run_in_executor, instead of the function called by it. This may need an extra layer of functions:
def blocking_get(self, var1):
# Does some time consuming call
# Mocking it
time.sleep(2)
resp_dict = {}
resp_dict['blah'] = "blah"
return resp_dict
@tornado.gen.coroutine
def get_helper(self, var1):
resp_dict = yield IOLoop.current().run_in_executor(None, self.blocking_get, var1)
IOLoop.current().call_later(10, self._call_later_something)
return resp_dict
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self, *args, **kwargs):
var1 = self.get_argument('var1', None)
resp_dict = yield self.get_helper(var1)
self.write(resp_dict)
self.finish()
Fantastic, thanks for this!
@javabrett thanks a lot! I have refactored my BaseHandler with your solution and now it looks like
class BaseHandler(web.RequestHandler):
@tornado.gen.coroutine
def get_async(self, *args):
logger = MXMLogger.instance().get_logger()
resp_dict = yield ioloop.IOLoop.current().run_in_executor(None, self.get_handler, args)
return resp_dict
def write_error(self, status_code, **kwargs):
# logger
logger = MXMLogger.instance().get_logger()
self.set_header('Content-Type', 'application/json')
err = {}
if self.settings.get("serve_traceback") and "exc_info" in kwargs:
# in debug mode, try to send a traceback
lines = []
for line in traceback.format_exception(*kwargs["exc_info"]):
lines.append(line)
err = {
'code': status_code,
'message': self._reason,
'traceback': lines
}
else:
err = {
'code': status_code,
'message': self._reason
}
# format error response
response = ResponseTornado()
response.addHeader(status_code=err['code'] )
response.addError(code=err['code'], status=err['code'], hint='', description='', message=err['message'])
logger.error("error %d %s" % (err['code'], err['message']) )
self.set_status(status_code)
self.write(json.dumps(response.getData()))
so in any other CustomHandler聽I would do
class CustomHandler(BaseHandler):
def get_handler(self, *args):
response = {}
# cpu bound call
return response
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self, *args):
response = yield self.get_async(args)
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.set_status(200)
self.write(json.dumps(response.getData()))
self.finish()
The only problem I have is that in the BaseHandler#get_async I have to reference the self.get_handler instance function, maybe I could pass it through the calls, not sure how and if it work will decorators...
Most helpful comment
This is because of how asyncio works which is used by default in Tornado 5.0 on Python versions that support it. See #2183 for details.