How to cancel previous requests when a new request comes from the same user in the same session

We create rest api using aiohttp. Our application is designed in such a way that the user sends requests more often than receiving responses (due to the calculation time). For the user, only the result of the last request is important. Is it possible to stop computation on obsolete queries?

thank

+3


source to share


2 answers


You are creating something very non-HTTP-like. An HTTP request should not take more than a few milliseconds to respond, and HTTP requests should not be interdependent; if you need to do computations that are taking quite a long time, either try to speed them up by changing your architecture / model / caching / whatever, or treat it explicitly as long running jobs that can be managed via the HTTP interface. This means that a "job" is a "physical resource" that can be requested over HTTP. You create a resource via a POST request:

POST /tasks
Content-Type: application/json

{"some": "parameters", "go": "here"}

      

{"resource": "/tasks/42"}

      

Then you can query the status of the task:

GET /tasks/42

      

{"status": "pending"}

      



And in the end get the results:

GET /tasks/42

      

{"status": "done", "results": [...]}

      

When you POST a new task that replaces the old one, your backend can cancel the old task in whatever way it sees fit; then the resource will return the status "canceled" or similar. After starting a new task, your client simply won't request the old resource.

Even if your client requests a resource once a second, it will still use fewer resources on the server (one connection open for a solid 10 seconds versus 10 connections open for 200ms during the same timeframe), especially if you apply some smart caching to it. It is also much more scalable as you can scale the task backend independently of the HTTP interface to it, and the HTTP interface can trivially scale across multiple servers and load balancers.

+3


source


I'll post the solution from @ Drizzt1991:

Hey, Artem. Quite an odd requirement you have. Understand that if 1 client is using a keep-alive socket, it is actually impossible to see the next request before responding to the first. This is how HTTP works, it waits for the result before sending another request. So your case will only work if the client is going to be running on 2 separate sockets, but again you need to argue that 2 sockets from the same client will be routed on the same machine. In practice, this does not work very well with rejection and so on. Basically it will be the Stateful API. Even if you do, not all libraries support cancellation. A traditional relational database will ignore the result, but will still process the pending request. It's ok if you are doing complex things like traversing a graph,and you have many steps to undo.

But if you are claiming that the client is using the socket pool and they are routed to the same computer and the requests benefit from cancellation, something like this should do the trick:



import asyncio
import random

from aiohttp import web


def get_session_id(request):
    # I don't know how you do session management, so left it out
    return ""


async def handle(request):
    session_id = get_session_id(request)
    request['tr_id'] = tr_id = int(random.random() * 1000000)

    running_tasks = request.app['running_tasks']
    if session_id in running_tasks and not running_tasks[session_id].done():
        running_tasks[session_id].cancel()
        del running_tasks[session_id]

    current_task = asyncio.ensure_future(_handle_impl(request))
    running_tasks[session_id] = current_task

    try:
        resp = await current_task
    except asyncio.CancelledError:
        print("Cancelled request", tr_id)
        resp = web.Response(text="Cancelled {}".format(tr_id))
    finally:
        if running_tasks[session_id] is current_task:
            del running_tasks[session_id]
    return resp


async def _handle_impl(request):
    tr_id = request['tr_id']
    print("Start request", tr_id)
    await asyncio.sleep(10)
    print("Finished request", tr_id)
    return web.Response(text="Finished {}".format(tr_id))


app = web.Application()
app.router.add_get('/', handle)
app.router.add_get('/{name}', handle)

app['running_tasks'] = {}

web.run_app(app, host="127.0.0.1", port=8080)

      

0


source







All Articles