tornadoweb / tornadoweb/tornado

websocket: Async version of WebSocketHandler.close is needed

Open
#2,914 6 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

websocket
Dominant language
Python
Stars
22.2k
Forks
5.6k
Avg merge
3h 42m
Merged PRs (30d)
16

Description

NOTE: this is different from https://github.com/tornadoweb/tornado/issues/2763. possibly related to https://github.com/tornadoweb/tornado/issues/2448, but also distinct.

After starting a websocket server, I am getting

ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending name='Task-5' coro=<RequestHandler._execute() running at /usr/local/lib/python3.8/site-packages/tornado/web.py:1703> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10acb2970>()]> cb=[_HandlerDelegate.execute.<locals>.<lambda>() at /usr/local/lib/python3.8/site-packages/tornado/web.py:2333]>

even after appropriately closing all connections. this is different from https://github.com/tornadoweb/tornado/issues/2763, where the unresolved task was WebSocketProtocol13._receive_frame_loop, and not all connections were closed.

to reproduce this easily, run the following tiny python3 program:

import asyncio
from abc import ABC
import tornado.websocket
import tornado.httpserver
import tornado.ioloop
import tornado.web

handlers = []
class Handler(tornado.websocket.WebSocketHandler, ABC):
    def open(self):  # making this async and awaiting write_message doesn't silence the tornado warnings.
        handlers.append(self)
    def on_message(self, message):
        pass
    def on_close(self):
        pass
    def check_origin(self, origin):
        return True
application = tornado.web.Application([
    (r'/', Handler),
])
http_server = tornado.httpserver.HTTPServer(application)
loop = tornado.ioloop.IOLoop.current()
async def my_function():  # closes the connections, stops the server, stops the loop after 20s.
    await asyncio.sleep(20.0)
    for handler in handlers:
        handler.close()
    http_server.stop()
    loop.stop()
http_server.listen(8080)
loop.add_callback(my_function)
loop.start()
loop.close()

in a separate Node.js console (for example), run:

const WebSocket = require('ws');
new WebSocket('ws://localhost:8080')

the culprit appears to be the future fut here, which is never awaited:
https://github.com/tornadoweb/tornado/blob/79b9c4fcbb3728f1325c4f6120bec45dd5df9fd8/tornado/web.py#L2323-L2326
tornado version: tornado-6.0.4. Python version: 3.8.5.

i am unable to further diagnose the issue, or fix it. thanks for your attention and time.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with tornado/web.py around the cited lines 2323-2326 and inspect how the reproduced WebSocketHandler is closed. Run the provided Python server with a Node.js WebSocket client, then verify the connection shutdown leaves no pending-task warning after the server and loop stop. Done means an asynchronous close path is available and the reproduction completes without the reported warning.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, networking
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.