chungkim271
chungkim271

Reputation: 967

asyncio.get_event_loop() throws error when FLASK_DEBUG=1

I have an flask app that closely follows fast.ai's render app: https://github.com/render-examples/fastai-v3. It uses asyncio to download the model while the page renders. The following code sets it up:

loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(setup_learner())] #setup_learner downloads the model
learn = loop.run_until_complete(asyncio.gather(*tasks))[0]
loop.close()

I noticed that when FLASK_DEBUG=1, the first line throws the error:

RuntimeError: There is no current event loop in thread 'Thread-1'.

But when FLASK_DEBUG=0, it doesn't. But the app is harder to debug. Has anyone come across this issue and what's causing it?

Upvotes: 4

Views: 731

Answers (1)

Michael Ruth
Michael Ruth

Reputation: 3504

The reason this works with FLASK_DEBUG=0 but not FLASK_DEBUG=1 is because the app runs in the main thread while not in debug mode, but doesn't run in the main thread while in debug mode.

The relevant code is in asyncio.events.BaseDefaultEventLoopPolicy.get_event_loop:

def get_event_loop():
    if (self._local._loop is None and
            not self._local._set_called and
            isinstance(threading.current_thread(), threading._MainThread)):
        self.set_event_loop(self.new_event_loop())

    if self._local._loop is None:
        raise RuntimeError('There is no current event loop in thread %r.'
                           % threading.current_thread().name)

    return self._local._loop

An event loop may be created only in the main thread with the current event loop policy: _UnixDefaultEventLoopPolicy in my case. Note that _UnixDefaultEventLoopPolicy subclasses BaseDefaultEventLoopPolicy but does not override BaseDefaultEventLoopPolicy.

Possible solutions:

  1. Create a new event loop in the current thread loop = asyncio.new_event_loop()
  2. Create a custom event loop policy that allows asyncio.get_event_loop() to create a new event loop in a thread other than the main thread by overriding the events.AbstractEventLoopPolicy.get_event_loop method. The safest way to do this is probably to subclass whichever event loop policy your environment is using and override its get_event_loop method, removing the main thread check. This way you retain all other policy behaviors, though the change may break some of these behaviors.

Either way, you're creating an event loop for the thread. My standard inclination is to go for simplicity, so #1.

So why doesn't the app run in the main thread while in debug mode? First, there are two processes while running in debug mode: the Flask server and the debugger, flask run and python pydevd.py, respectively. The main thread of each process has an event loop that facilitates communication between the app server and the debugger - among other things, and spawns another thread in which the app actually runs. You'll also see this behavior without debug mode enabled if the application is being served by a multi-threaded app server, e.g. gunicorn or uwsgi.

Flask doesn't really support asyncio. Sure one can use it with Flask, but no guarantees are made about their compatibility. See this issue, and more specifically: the issue referenced in its comments

Upvotes: 3

Related Questions