Python – Flask-SocketIO send message from thread: not always working

Question:

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?

Asked By: Schnodderbalken

||

Answers:

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

Answered By: Schnodderbalken

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.

Answered By: 殷明军

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()
Answered By: Alessandro Roaro