miguelgrinberg / flask-sock

Modern WebSocket support for Flask.
MIT License
272 stars 24 forks source link

When used with eventlet, exception shown after multiple connections #50

Open wmww opened 1 year ago

wmww commented 1 year ago

The code (minimal example that simply closes websocket connections immediately upon creation):

import flask
import flask_sock
import eventlet
from eventlet import wsgi

eventlet.monkey_patch()

app = flask.Flask(__name__)
sock = flask_sock.Sock(app)

@sock.route('/websocket')
def websocket(ws):
    ws.close()

if __name__ == '__main__':
    wsgi.server(eventlet.listen(("127.0.0.1", 5055)), app)

To test a connection:

wscat --connect 'ws://localhost:5055/websocket'

It gets disconnected (as we expect), but if you run it again a long error is printed in the Python output:

(1572383) wsgi starting up on http://127.0.0.1:5055
(1572383) accepted ('127.0.0.1', 51362)
127.0.0.1 - - [12/Mar/2023 17:33:25] "GET /websocket HTTP/1.1" 200 0 0.001137
(1572383) accepted ('127.0.0.1', 51378)
Exception in thread Thread-1 (_thread):
Traceback (most recent call last):
  File "/home/wmww/.local/lib/python3.10/site-packages/eventlet/greenio/base.py", line 364, in _recv_loop
    self._read_trampoline()
  File "/home/wmww/.local/lib/python3.10/site-packages/eventlet/greenio/base.py", line 332, in _read_trampoline
    self._trampoline(
  File "/home/wmww/.local/lib/python3.10/site-packages/eventlet/greenio/base.py", line 211, in _trampoline
    return trampoline(fd, read=read, write=write, timeout=timeout,
  File "/home/wmww/.local/lib/python3.10/site-packages/eventlet/hubs/__init__.py", line 159, in trampoline
    return hub.switch()
  File "/home/wmww/.local/lib/python3.10/site-packages/eventlet/hubs/hub.py", line 313, in switch
    return self.greenlet.switch()
eventlet.hubs.IOClosed: [Errno 107] Operation on closed file

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.10/threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "/home/wmww/.local/lib/python3.10/site-packages/simple_websocket/ws.py", line 160, in _thread
    in_data = self.sock.recv(self.receive_bytes)
  File "/home/wmww/.local/lib/python3.10/site-packages/eventlet/greenio/base.py", line 370, in recv
    return self._recv_loop(self.fd.recv, b'', bufsize, flags)
  File "/home/wmww/.local/lib/python3.10/site-packages/eventlet/greenio/base.py", line 367, in _recv_loop
    raise EOFError()
EOFError
127.0.0.1 - - [12/Mar/2023 17:33:28] "GET /websocket HTTP/1.1" 200 0 0.000883

What's the cause of this, and can it be prevented or safely ignored?

flask version: 2.2.3 flask-sock version: 0.6.0 eventlet version: 0.33.3

wmww commented 1 year ago

If I don't call eventlet.monkey_patch() this problem doesn't happen, the only downside I've seen of that is that I have to send SIGINT (ctrl+C) to the python server twice to make it quit, and it shows an error on quitting:

(1595614) wsgi starting up on http://127.0.0.1:5055
(1595614) accepted ('127.0.0.1', 36448)
127.0.0.1 - - [12/Mar/2023 17:44:29] "GET /websocket HTTP/1.1" 200 0 0.001205
^Cwsgi exiting
(1595614) wsgi exited, is_accepting=True

<it's stuck here until I press Ctrl+C again>

^CException ignored in: <module 'threading' from '/usr/lib/python3.10/threading.py'>
Traceback (most recent call last):
  File "/usr/lib/python3.10/threading.py", line 1567, in _shutdown
    lock.acquire()
KeyboardInterrupt: 

This isn't a problem if I monkey patch.

miguelgrinberg commented 1 year ago

It looks like eventlet's socket implementation doesn't behave exactly like Python's original socket class. I would not recommend removing the monkey patching. Instead you can add a ping_interval, which also indirectly addresses the issue:

import eventlet
eventlet.monkey_patch()

import flask
import flask_sock
import eventlet
from eventlet import wsgi

app = flask.Flask(__name__)
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 25}
sock = flask_sock.Sock(app)

@sock.route('/websocket')
def websocket(ws):
    ws.close()

if __name__ == '__main__':
    wsgi.server(eventlet.listen(("127.0.0.1", 5055)), app)