Gunicorn gevent: There is no current event loop in thread

Viewed 1239

I have an application using async io to perform parallel requests to different services. It's a flask APP running with Gunicorn.

Unfortunately, some of the requests are kind of long (up to 10s). Until now, I was using the base worker from Gunicorn (sync), but since they are a finited number, I sometime run out of them.

So I've heard about the gevent worker class which for most of the requests allows me to process in parallel, but I don't get how I'm supposed to deal with the code using asyncio. I've reproduced my issue with this simple example :

I use this command to start the server :

gunicorn test_wsgi:app --config=test_wsgi_config.py

With test_wsgi.py:

import asyncio

from flask import Flask

app = Flask(__name__)


async def a_long_task():
    await asyncio.sleep(5)


@app.route('/')
def hello():
    loop = asyncio.get_event_loop()

    loop.run_until_complete(
        loop.create_task(a_long_task())
    )

    return f'Hello, world'

And test_wsgi_config.py

worker_class = "gevent"

When I use worker_class = sync, it works fine, but all the requests are queued. But with gevent, I keep having :

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

If I create an event loop :

@app.route('/')
def hello():
    asyncio.set_event_loop(asyncio.new_event_loop())
    loop = asyncio.get_event_loop()

    loop.run_until_complete(
        loop.create_task(a_long_task())
    )

    return f'Hello, world'

I get :

RuntimeError: This event loop is already running

When I do several commands likes this one :

curl 127.0.0.1:8000 &

I'm not sure of how I'm supposed to deal with that.

1 Answers

I managed to get your example working by making a small change to how you get your event loop:

import asyncio

from flask import Flask

app = Flask(__name__)


async def a_long_task():
    await asyncio.sleep(5)


@app.route('/')
def hello():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    loop.run_until_complete(
        loop.create_task(a_long_task())
    )

    return f'Hello, world'

This works because we get a new event loop, which should always work, then set it as the current one.

Related