Python - Flask-SocketIO send message from thread: not always working
Asked Answered
C

3

31

I am in the situation where I receive a message from the client. Within the function that handles that request (@socketio.on) I want to call a function where some heavy work is done. This should not result in blocking the main thread and the client is thought to be informed once the work is done. Thus I start a new thread.

Now I encounter a really strange behavior: The message never reaches the client. However, the code reaches that particular spot where the message is sent. Even more surprising is the fact that if there is nothing happening in the thread except for the message being sent to the client then the answer actually finds its way to the client.

To sum it up: If something computationally intensive happens before the message is sent it is not being delivered, otherwise it is.

Like it is said here and here, sending messages from a thread to the clients is not a problem at all:

In all the examples shown until this point the server responds to an event sent by the client. But for some applications, the server needs to be the originator of a message. This can be useful to send notifications to clients of events that originated in the server, for example in a background thread.

Here is a sample code. When removing the commenting sharps (#) the message ('foo from thread') does not find its way to the client, otherwise it does.

from flask import Flask
from flask.ext.socketio import SocketIO, emit
app = Flask(__name__)
socketio = SocketIO(app)

from threading import Thread
import time 

@socketio.on('client command')
def response(data):
    thread = Thread(target = testThreadFunction)
    thread.daemon = True
    thread.start()

    emit('client response', ['foo'])

def testThreadFunction():
#   time.sleep(1)

    socketio.emit('client response', ['foo from thread'])

socketio.run(app)

I am using Python 3.4.3, Flask 0.10.1, flask-socketio1.2, eventlet 0.17.4.

This sample can be copied and pasted in a .py file and the behavior can be instantly reproduced.

Can somebody explain this strange behavior?

Update

It seems to be a bug of eventlet. If I do:

socketio = SocketIO(app, async_mode='threading')

It forces the application not to use eventlet although it is installed.

However, this is not an applicable solution for me as using 'threading' as async_mode refuses to accept binary data. Every time I send some binary data from the client to the server it says:

WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.

The third option, using gevent as the async_mode does not work for me as well as gevent does not have support for python 3 yet.

So any other suggestions?

Collusion answered 3/1, 2016 at 20:29 Comment(5)
@rfkortekaas how does that make sense in an asynchronous protocol? And apart from that the response is sent. That is what "emit" does. The other thing is happening in a separate thread.Collusion
I removed my comment as I misread the question.Fixate
I ran into a very similar issue, but we were doing nothing computationally expensive. It seems to be an issue with grabbing the proper "socketio" when calling socketio.emit. Because the emit DOES happen, but no clients receive the message. Adding async_mode='threading' seemed to fix the problem. But hopefully there's a better wayParaboloid
@Paraboloid Do you think that this is really the problem? Using the Werkzeug based threading is not recommended for use in production as it lacks performance. Additionally it only supports long-polling transport. Monkeypatching eventlet did not help?Collusion
@Collusion monkey patching helped some stuff, but broke others. So, it's not much of a solution unfortunatelyParaboloid
C
17

I managed to resolve the issue by monkeypatching several Python functions which causes Python to use the eventlet functions instead of the native ones. This way background threads work fine with eventlet.

https://github.com/miguelgrinberg/Flask-SocketIO/blob/e024b7ec9db4837196d8a46ad1cb82bc1e15f1f3/example/app.py#L30-L31

Collusion answered 4/1, 2016 at 19:17 Comment(0)
N
7

I have the same problem. But I think I found out what's matter.

When starting SocketIO with the following code and create the threading like yours, the client CAN NOT receive the message which emitted by server.

socketio.run()

I find out that the flask_socketio offer a function named start_background_task from document.

Here are the description of it.

start_background_task(target, *args, **kwargs)

Start a background task using the appropriate async model. This is a utility function that applications can use to start a background task using the method that is compatible with the selected async mode.

Parameters:

target – the target function to execute. args – arguments to pass to the function. kwargs – keyword arguments to pass to the function. This function returns an object compatible with the Thread class in the Python standard library.

The start() method on this object is already called by this function.

So I replace my code thread=threading(target=xxx) with socketio.start_background_task(target=xxx) then socketio.run() . The server get stuck in the thread when run into it ,which means that the function start_background_task only returned after the thread finished.

Then I try to use gunicorn to run my server with gunicorn --worker-class eventlet -w 1 web:app -b 127.0.0.1:5000

Then everything works well!

So let start_background_task choose a proper way to start a thread.

Nananne answered 21/3, 2018 at 16:6 Comment(1)
I don't understand. Background: I might have the same problem. I've an application running a thread. This thread should emit status frequently that is sent over a websocket to a web page. But only the first (!) message get's sent through. a) I don't understand what the technical reasons for such a behavior. Could you explain that? And b) As my background thread can not be changed by me what solution would there be to send data to a client if start_background_task() is apparently not an option?Salomesalomi
C
3

The problem that you are running into is caused by the fact that eventlet and gevent (two threading modes of socket.io) don't support multiprocessing. So this is not a bug, but the way that is implemented. In order to make it work you can use async_mode=threading, or you can monkey-patch evenlet to enable the usage of a background thread.

socketio = SocketIO(app, async_mode='eventlet')
import eventlet
eventlet.monkey_patch()
Christianly answered 23/11, 2020 at 14:24 Comment(1)
Using async_mode=threading helped to stop start_background_task from blocking in a Flask serverChancre

© 2022 - 2024 — McMap. All rights reserved.