miguelgrinberg / flask-sock

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

flask-sock failing for me in gunicorn/gunicorn3 #39

Closed za3k closed 1 year ago

za3k commented 1 year ago

I have what I think is a fairly simple pub-sub application. Source is on github. The main file takes requests at "/ajax//send", parses them as json, and then sends them to anyone listening to a websocket "/ws/".

import flask
from flask_sock import Sock
from base import app,ajax

[...definition of 'rooms', other routes...]

sock = Sock(app)
@sock.route("/ws/<room_id>")
def hang_listen(ws, room_id):
    with rooms[room_id].listener() as reader:
        while True:
            ws.send(reader.next())

@ajax("/ajax/<room_id>/send")
def hang_send(j, room_id):
    rooms[room_id].put(json.dumps(j))
    return {"success": True}

It works great with flask development mode. In gunicorn or gunicorn3, the websocket connection seems to fail on the browser side. I don't see an error from gunicorn.

Let me know if you need a simpler one-file repo case. There are some weird debug vs production path differences, but I'm pretty sure it's not those (I took them out to test).

This is as much a request for help as a bug report, since I can't pinpoint what's failing.

Edit: Oh, also you can see the live failing site at https://tilde.za3k.com/hackaday/hang/public

miguelgrinberg commented 1 year ago

the websocket connection seems to fail on the browser side

Please provide all the details about the failure including client and/or server side relevant logs.

za3k commented 1 year ago

I've made a simplified server:

#!/bin/python3
import flask
from flask_sock import Sock

app = flask.Flask(__name__)

@app.route("/")
def index():
    return ""

sock = Sock(app)
@sock.route("/ws")
def hang_listen(ws):
    while True:
        ws.send("Spam")

Invoked gunicorn:

gunicorn -e SCRIPT_NAME=/hackaday/hang --bind 0.0.0.0:5000 app:app

And manually ran one javascript line from the chromium console:

w = new WebSocket("ws://localhost:5000/hackaday/hang/ws")

This no longer reproduces for me.

HOWEVER, if I add the line time.sleep(50) inside the socket, I get the same problems (nearly, the original server had an unrelated exception, which caused slightly different behavior on the client).

The client side reports no problems. I think the web socket hangs up eventually, but I can't prove it.

The server log prints

[2022-11-20 12:43:06 -0500] [19508] [CRITICAL] WORKER TIMEOUT (pid:19604)
[2022-11-20 12:43:06 -0500] [19604] [INFO] Worker exiting (pid: 19604)
[2022-11-20 12:43:07 -0500] [19508] [WARNING] Worker with pid 19604 was terminated due to signal 9

I'm guessing this is something to do with gunicorn timeouts, or sync workers. I'm not sure if flask-sock is involved--seems likely no. I tried app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 25} in case that helped. It did not.

The relevant line in the original program triggering this is queue.get(). I'd of course prefer this not time out the websocket, but I'm also not sure why it's happening at all--something is failing in my queue process, too. Probably something with threading/multiprocessing is making a new queue for each request.

P.S. You should link https://blog.miguelgrinberg.com/post/add-a-websocket-route-to-your-flask-2-x-application in your README/docs. It's better than just the docs alone.

miguelgrinberg commented 1 year ago

Well, yes, if you sleep or do anything that blocks for a long time then the WebSocket is not being serviced. Gunicorn has a 30 second timeout for socket activity, so you should never leave the socket unattended for that long, or else extend or disable the timeout, which is entirely outside of the scope of this project and should be done directly in Gunicorn configuration.

za3k commented 1 year ago

P.S. Thanks for writing flask-sock!