3

I have written a basic REST API using aiohttp, a simplified version of which is included below to illustrate the problem I am looking to solve.

The API has two endpoints - each of which calls a function that performs some calculations. The difference between the two is that for one of the endpoints, the calculations take 10 seconds, and for the other they take only 1 second.

My code is below (the actual calculations have been replaced with time.sleep() calls).

import time
from aiohttp import web


def simple_calcs():
    time.sleep(1)  # Pretend this is the simple calculations
    return {'test': 123}

def complex_calcs():
    time.sleep(10)  # Pretend this is the complex calculations
    return {'test': 456}


routes = web.RouteTableDef()

@routes.get('/simple_calcs')
async def simple_calcs_handler(request):
    results = simple_calcs()
    return web.json_response(results)

@routes.get('/complex_calcs')
async def complex_calcs_handler(request):
    results = complex_calcs()
    return web.json_response(results)


app = web.Application()
app.add_routes(routes)
web.run_app(app)

What I would like to happen:

If I send a request to the slower endpoint, then immediately afterwards send a request to the faster endpoint, I would like to receive a response from the faster endpoint first while the slower calculations are still ongoing.

What actually happens:

The calculations being carried out by the slower endpoint are blocking. I receive the response from the slow endpoint after ~10 seconds and from the fast endpoint after ~11 seconds.

I've spent the last few hours going round in circles, reading up on asyncio and multiprocessing, but unable to find anything that could solve my problem. Probably I need to spend a bit longer studying this area to gain a better understanding, but hoping I can get a push in the right direction towards the desired outcome.

sjw
  • 6,213
  • 2
  • 24
  • 39
  • 1
    Asyncio goes really bad with CPU bound tasks, multiprocessing is the way to solve this, [aioprocessing](https://github.com/dano/aioprocessing) does a very good job mixing both – yorodm Sep 14 '18 at 17:52
  • @yorodm thanks, will have a read. – sjw Sep 14 '18 at 17:56

1 Answers1

3

Any blocking IO calls should be avoided in asyncio.

Essentially time.sleep(10) blocks the whole aiohttp server for 10 seconds.

To solve it please use loop.run_in_executor() call:

async def complex_calcs():
    loop = asyncio.get_event_loop()
    loop.run_in_executor(None, time.sleep, 10)  # Pretend this is the complex calculations
    return {'test': 456}
Andrew Svetlov
  • 16,730
  • 8
  • 66
  • 69
  • 1
    Thank you - this got me on the right path, but I should add for anyone else reading that I had to insert `None` as the first argument: `loop.run_in_executor(None, time.sleep, 10)`. – sjw Sep 15 '18 at 17:20
  • 1
    Thanks, updated the answer to add `None` – Andrew Svetlov Sep 16 '18 at 18:25