Add_task that contains an infinite loop will cause an error when stopping the server

Hi,

I’m following the Websocket code here https://gist.github.com/ahopkins/5b6d380560d8e9d49e25281ff964ed81 and at some point, we add a task that contains a while True

request.app.add_task(channel.receiver())
...

Of course, this kind of makes sense, but is there a way to avoid having the

Task was destroyed but it is pending!

error in the terminal everytime we restart the server, and, instead, gracefully stop the running loop?

yes. will send you a snippet when I’m back to keyboard.

FWIW, v21.12 includes some nice utilities around this.

Wow, curious to see what you implemented on 21.12!

When will it be released ? (if not already)

Version 21.12 will be released Sunday (2021-12-26). I am working thru finalizing the docs right now. The features I was specifically thinking of are from PR #2304.

task1 = app.add_task(dummy, name="dummy_task")
task2 = app.get_task("dummy_task")

assert task1 is task2

app.cancel_task("dummy_task")

for task in app.tasks:
    print(task.is_cancelled())

app.purge_tasks()

To answer the question at hand, see below.

async def background():
    try:
        print("Sleeping in the background")
        await asyncio.sleep(20)
    finally:
        print("[FINALLY] background")


@app.websocket("/q")
async def handler(request, ws):
    request.app.add_task(background())
    try:
        while True:
            print(".")
            await asyncio.sleep(1)
    finally:
        print("[FINALLY] handler")


@app.before_server_stop
async def cleanup(app, _):
    for task in asyncio.all_tasks():
        if task.get_coro().__name__ == "background":
            task.cancel()

What you want to do is call cancel on the task. Unfortunately, in v21.9 there is no good way to get access to that task to be able to call cancel on it (this was the reason it is being added to v21.12). Therefore, you have to loop thru the all_tasks to find the one that you want. Not ideal.

1 Like

Perfect, thank you!

Just a quick note, the “cancel_task” is a coroutine that needs await :

await app.cancel_task("dummy_task")

(For future readers)