'Channels websocket AsyncJsonWebsocketConsumer disconnect not reached

I have the following consumer:

class ChatConsumer(AsyncJsonWebsocketConsumer):
    pusher = None

    async def connect(self):
        print(self.scope)
        ip = self.scope['client'][0]
        print(ip)
        self.pusher = await self.get_pusher(ip)
        print(self.pusher)
        await self.accept()

    async def disconnect(self, event):
        print("closed connection")
        print("Close code = ", event)
        await self.close()
        raise StopConsumer

    async def receive_json(self, content):
        #print(content)
        if 'categoryfunctionname' in content:
            await cellvoltage(self.pusher, content)
        else:
            print("ERROR: Wrong data packets send")
            print(content)


    @database_sync_to_async
    def get_pusher(self, ip):
        p = Pusher.objects.get(auto_id=1)
        try:
            p = Pusher.objects.get(ip=ip)
        except ObjectDoesNotExist:
            print("no pusher found")
        finally:
            return p

Connecting, receiving and even getting stuff async from the database works perfectly. Only disconnecting does not work as expected. The following Terminal Log explains what's going on:

2018-09-19 07:09:56,653 - INFO - server - HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2018-09-19 07:09:56,653 - INFO - server - Configuring endpoint tcp:port=1111:interface=192.168.1.111
2018-09-19 07:09:56,653 - INFO - server - Listening on TCP address 192.168.1.111:1111
[2018/09/19 07:11:25] HTTP GET / 302 [0.02, 10.171.253.112:35236]
[2018/09/19 07:11:25] HTTP GET /login/?next=/ 200 [0.05, 10.111.253.112:35236]
{'type': 'websocket', 'path': '/ws/chat/RP1/', 'headers': [(b'upgrade', b'websocket'), (b'connection', b'Upgrade'), (b'host', b'10.111.111.112:1111'), (b'origin', b'http://10.111.253.112:1111'), (b'sec-websocket-key', b'vKFAnqaRMm84AGUCxbAm3g=='), (b'sec-websocket-version', b'13')], 'query_string': b'', 'client': ['10.111.253.112', 35238], 'server': ['192.168.1.111', 1111], 'subprotocols': [], 'cookies': {}, 'session': <django.utils.functional.LazyObject object at 0x7fe4a8d1ba20>, 'user': <django.utils.functional.LazyObject object at 0x7fe4a8d1b9e8>, 'path_remaining': '', 'url_route': {'args': (), 'kwargs': {'room_name': 'RP1'}}}
10.111.253.112
[2018/09/19 07:11:25] WebSocket HANDSHAKING /ws/chat/RP1/ [10.111.253.112:35238]
[2018/09/19 07:11:25] WebSocket CONNECT /ws/chat/RP1/ [10.111.111.112:35238]
no pusher found
1 - DEFAULT - 0.0.0.0
ERROR: Wrong data packets send
{'hello': 'Did I achieve my objective?'}
[2018/09/19 07:11:46] WebSocket DISCONNECT /ws/chat/RP1/ [10.111.253.112:35238]
2018-09-19 07:11:56,792 - WARNING - server - Application instance <Task pending coro=<SessionMiddlewareInstance.__call__() running at /home/pi/PycharmProjects/LOGGER/venv/lib/python3.6/site-packages/channels/sessions.py:175> wait_for=<Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/lib/python3.6/asyncio/futures.py:403, <TaskWakeupMethWrapper object at 0x7fe4a82e6fd8>()]>> for connection <WebSocketProtocol client=['10.171.253.112', 35238] path=b'/ws/chat/RP1/'> took too long to shut down and was killed.

After the 10 seconds timeout it gives a warning that the connection was killed:

WARNING - server - Application instance taskname running at location at linenumber for connection cxn-name took too long to shut down and was killed.

The disconnect method was thus also not reached.

  • What could this be?
  • Am I using the correct method?
  • Could I expand the timeout period?


Solution 1:[1]

if you are intending to run some custom logic during connection close then you should override websocket_disconnect and then call super (rather than rais the exception yourself)

https://github.com/django/channels/blob/507cb54fcb36df63282dd19653ea743036e7d63c/channels/generic/websocket.py#L228-L241

Solution 2:[2]

The code linked in the other answer was very helpful. Here it is for reference (as of May 2022):

    async def websocket_disconnect(self, message):
        """
        Called when a WebSocket connection is closed. Base level so you don't
        need to call super() all the time.
        """
        try:
            for group in self.groups:
                await self.channel_layer.group_discard(group, self.channel_name)
        except AttributeError:
            raise InvalidChannelLayerError(
                "BACKEND is unconfigured or doesn't support groups"
            )
        await self.disconnect(message["code"])
        raise StopConsumer()

    async def disconnect(self, code):
        """
        Called when a WebSocket connection is closed.
        """
        pass

I'd note that there is no need to use super() on websocket_disconnect. They have a hook disconnect which was perhaps added since the original answer that can be used to add any teardown code. You can simply override the disconnect method in your own class and it will be called.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Matthaus Woolard
Solution 2 Nick Brady