profile
viewpoint

Ask questionsSIGINT handler does not disconnect client

When attempting to disconnect client, it only disconnects once, but then reconnects.

Connection log:

DEBUG:asyncio:Using selector: KqueueSelector
INFO:engineio.client:Attempting WebSocket connection to wss://myproject.com/api/ws/?transport=websocket&EIO=3
INFO:engineio.client:WebSocket connection accepted with {'sid': '7a2f537ad5cc49e79c5f05fa3644c923', 'upgrades': [], 'pingTimeout': 60000, 'pingInterval': 25000}
INFO:socketio.client:Engine.IO connection established
INFO:engineio.client:Sending packet PING data None
INFO:engineio.client:Received packet MESSAGE data 0
INFO:socketio.client:Namespace / is connected
INFO:engineio.client:Received packet PONG data None

After sending SIGINT:

INFO:engineio.client:Sending packet PING data None
ERROR:engineio.client:packet queue is empty, aborting
INFO:engineio.client:Exiting write loop task
INFO:engineio.client:Unexpected error receiving packet: "", aborting
INFO:engineio.client:Waiting for write loop task to end
INFO:engineio.client:Waiting for ping loop task to end
INFO:engineio.client:PONG response has not been received, aborting
INFO:engineio.client:Exiting ping task
INFO:socketio.client:Engine.IO connection dropped
WARNING:project:Disconnected
INFO:engineio.client:Exiting read loop task
INFO:socketio.client:Connection failed, new attempt in 0.91 seconds
INFO:engineio.client:Attempting WebSocket connection to wss://myproject.com/api/ws/?transport=websocket&EIO=3
INFO:engineio.client:WebSocket connection accepted with {'sid': '3c5ebff507024dadbb293e9d1adfcad7', 'upgrades': [], 'pingTimeout': 60000, 'pingInterval': 25000}
INFO:socketio.client:Engine.IO connection established
INFO:socketio.client:Reconnection successful
INFO:engineio.client:Sending packet PING data None
INFO:engineio.client:Received packet MESSAGE data 0
INFO:socketio.client:Namespace / is connected
INFO:engineio.client:Received packet PONG data None

After another SIGINT:

^CERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<AsyncClient.disconnect() running at /Users/asnelzin/dev/project/venv/lib/python3.7/site-packages/engineio/asyncio_client.py:106>>
/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/base_events.py:604: RuntimeWarning: coroutine 'AsyncClient.disconnect' was never awaited
  self._ready.clear()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Traceback (most recent call last):
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/base_events.py", line 566, in run_until_complete
    self.run_forever()
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/base_events.py", line 534, in run_forever
    self._run_once()
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/base_events.py", line 1735, in _run_once
    event_list = self._selector.select(timeout)
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/selectors.py", line 558, in select
    kev_list = self._selector.control(None, max_ev, timeout)
  File "/Users/asnelzin/dev/project/venv/lib/python3.7/site-packages/engineio/client.py", line 43, in signal_handler
    return original_signal_handler(sig, frame)
  File "/Users/asnelzin/dev/project/venv/lib/python3.7/site-packages/socketio/client.py", line 26, in signal_handler
    return original_signal_handler(sig, frame)
KeyboardInterrupt

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/Users/asnelzin/dev/project/project/__main__.py", line 93, in <module>
    asyncio.run(main(sys.argv[1:]))
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/runners.py", line 46, in run
    _cancel_all_tasks(loop)
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/runners.py", line 62, in _cancel_all_tasks
    tasks.gather(*to_cancel, loop=loop, return_exceptions=True))
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/base_events.py", line 566, in run_until_complete
    self.run_forever()
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/base_events.py", line 534, in run_forever
    self._run_once()
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/base_events.py", line 1735, in _run_once
    event_list = self._selector.select(timeout)
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/selectors.py", line 558, in select
    kev_list = self._selector.control(None, max_ev, timeout)
  File "/Users/asnelzin/dev/project/venv/lib/python3.7/site-packages/engineio/client.py", line 43, in signal_handler
    return original_signal_handler(sig, frame)
  File "/Users/asnelzin/dev/project/venv/lib/python3.7/site-packages/socketio/client.py", line 26, in signal_handler
    return original_signal_handler(sig, frame)
KeyboardInterrupt
ERROR:asyncio:Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10c7e0310>
ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<AsyncClient._ping_loop() running at /Users/asnelzin/dev/project/venv/lib/python3.7/site-packages/engineio/asyncio_client.py:446> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10c7ed790>()]>>
ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<AsyncClient._write_loop() running at /Users/asnelzin/dev/project/venv/lib/python3.7/site-packages/engineio/asyncio_client.py:551> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10c7eda50>()]>>
ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<AsyncClient._read_loop_websocket() running at /Users/asnelzin/dev/project/venv/lib/python3.7/site-packages/engineio/asyncio_client.py:500> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10c0af5d0>()]> cb=[<TaskWakeupMethWrapper object at 0x10c7ed9d0>()]>
ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<Queue.get() running at /Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/queues.py:159> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10c7ed2d0>()]> cb=[_release_waiter(<Future pendi...10c7eda50>()]>)() at /Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/tasks.py:392]>
Exception ignored in: <coroutine object Queue.get at 0x10c806710>
Traceback (most recent call last):
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/queues.py", line 161, in get
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/base_events.py", line 683, in call_soon
  File "/Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/base_events.py", line 475, in _check_closed
RuntimeError: Event loop is closed
ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<Event.wait() running at /Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/locks.py:293> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10c7ed7d0>()]> cb=[_release_waiter(<Future pendi...10c7ed790>()]>)() at /Users/asnelzin/.pyenv/versions/3.7.4/lib/python3.7/asyncio/tasks.py:392]>
miguelgrinberg/python-socketio

Answer questions chrisdlangton

I also noticed this in my project.

Tried fixing it using some process managers, supervisord / monit / circusd. It took 9 weeks of after work time and I finally realised my bug was a little more trouble then your report might seem..

I immediately started using the atexit.register builtin for Client.disconnect and forgot all this time I introduced it "after" noticing the bug!

Long story short; this library is not signal aware and it is incompatible with the atexit python builtin (doing this actually turns your process into a zombie).

You need to do 2 things;

  1. Add some signal handlers;
import socketio
from retry.api import retry

@retry((Exception), tries=15, delay=1.5, backoff=3)
def close_socket():
    sio.disconnect()

sio = socketio.Client()
def signal_handler(signum, **_):
    message = f'Signal handler called with signal {signum}'
    log.warning(message)
    # do things
    close_socket()
    sys.exit(0)

if __name__ == "__main__":
    signal.signal(signal.SIGQUIT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    signal.signal(signal.SIGTSTP, signal_handler) # ctrl+z
    signal.signal(signal.SIGINT, signal_handler) # ctrl+c
    # do things
    close_socket()
    sys.exit(0)
  1. Make sure every single possible exit location calls close_socket (because atexit will zombie this library)

Hope that helps you too

Maintainers You should consider fixing your incompatibility with python atexit builtin, being a zombie is not a good look.. or document the atexit limitation (and how to disconnect safely) in the getting started guide.

useful!

Related questions

Strange behaviours when pingInterval > pingTimeout hot 2
Invalid async_mode specified hot 2
How to terminate a disconnected client? hot 2
Is it possible to: Retrieve responses that do not have an event name? hot 1
ImportError: cannot import name 'Namespace' from 'socketio' (/usr/lib/python3.7/site-packages/socketio/__init__.py) hot 1
AttributeError: 'module' object has no attribute 'Server' hot 1
python-socketio can not connect to flask server hot 1
Python Server & Node Client , On Authentication Fail client receives 1 fix error in any scenario. hot 1
No disconnect event triggered after receiving no pong hot 1
How to resolve multiple CORS values in socketio requests, Sanic? hot 1
source:https://uonfu.com/
Github User Rank List