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

Schnodderbalken picture Schnodderbalken · Jan 3, 2016 · Viewed 16.7k times · Source

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?

Answer

Schnodderbalken picture Schnodderbalken · Jan 4, 2016

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