Search code examples
pythonpython-2.7socket.ioeventletpython-socketio

Getting results from Queue to emit to socket.io correctly


I need to start a server in one thread, a value producer in another thread (impersonated here by mock_producer), and the server's background thread is supposed to take each value from the queue and emit it to the client. At the same time, the WSGI server should serve the index.html when requested. Here's the best attempt so far:

# pip install eventlet python-socketio

from threading import Thread
from Queue import Queue
import eventlet
import socketio

def mock_producer(queue):
    import time
    import itertools
    for count in itertools.count():
        queue.put(count)
        time.sleep(5)

def background():
    while True:
        if not queue.empty():
            value = queue.get()
            sio.emit('value', value);
        sio.sleep(0.1)

sio = socketio.Server(logger=True)
app = socketio.WSGIApp(sio, static_files={
    '/': 'index.html',
})
queue = Queue()
prod_thread = Thread(target=mock_producer, args=(queue,))
prod_thread.start()
ws_server = eventlet.listen(('', 5000))
ws_thread = sio.start_background_task(background)
eventlet.wsgi.server(ws_server, app)

with the accompanying toy index.html:

<!doctype html>
<html>
  <head>
    <title>Test</title>
    <script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.slim.js"></script>
    <script>
      const socket = io.connect();
      socket.on('value', value => console.log(value));
    </script>
  </head>
  <body></body>
</html>

The thing that bugs me is the sio.sleep(0.1) line. This obviously introduces a delay (however small) between an object being put into a queue, and the object being served to the client. But this doesn't work:

def background():
    while True:
        value = queue.get()
        sio.emit('value', value);

The reason is, queue.get() blocks, which doesn't let the WSGI server serve the index.html page (which apparently happens on the same thread).

When I tried launching a new thread for the queue.get-emit loop (e.g. using Thread(target=background).start() instead of sio.start_background_task(background)), the debug output was claiming emit was happening, but nothing was reaching the client, so that was a bust, too.

Ideally, I'd like the code to be idle till either a request needs to be processed or a queue has a value, and react immediately to either.

Is there a way to write this cleanly?

NB: Unfortunately, stuck in Python 2 for this project due to a crucial dependency. I believe the only consequence is the import Queue from Queue line, but just in case.


Solution

  • Eventlet uses cooperative multitasking. Any time you use potentially blocking functions from the standard library such as those in threading, synchronization or sockets, you are at risk of blocking the whole server.

    Eventlet provides alternative versions of most blocking functions in the library, so you should use those to avoid this type of problem. The easiest way to switch to the eventlet-friendly functions is to monkey-patch the standard library.