Tornado: RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'

Created on 10 Mar 2018  路  6Comments  路  Source: tornadoweb/tornado

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.

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.

All 6 comments

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...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aie0 picture aie0  路  4Comments

Lucaszw picture Lucaszw  路  5Comments

mirceaulinic picture mirceaulinic  路  3Comments

zooba picture zooba  路  5Comments

christippett picture christippett  路  5Comments