1

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.

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • Did you monkey patch the standard library for eventlet compatibility? – Miguel Grinberg Nov 29 '19 at 15:03
  • @Miguel I did not (truthfully I am rather ignorant of eventlet, and hadn't known about `monkey_patch` till just now). I was following the python-socketio docs (which apparently mentions monkey_patch, but under gunicorn heading that I missed, but says that python-socketio itself doesn't need patching) and wsgi server example (which does not monkey_patch). I can't test till Monday, but assuming that's the issue, how would I need to write the program? Would my `Thread(target=background).start()` line start doing what I expected it to? – Amadan Nov 30 '19 at 02:42
  • Threads and queues need to be monkey patched to work well with eventlet. Without the monkey patching they block the server. I'm not completely sure this will address all the problems, but it is required in your case, so give it a try, then we can talk about any remaining problems. – Miguel Grinberg Nov 30 '19 at 17:00
  • @Miguel Thank you. Your solution worked on the toy example above, but monkey-patching broke the "crucial dependency" (which is the real producer in my code, and which I wouldn't touch with a ten foot Polish person if I had a choice :P ). I'll have to live with the inelegant hack above... Since you answered the question as asked, if you want to write it up as an answer, you got the checkmark. – Amadan Dec 02 '19 at 12:31

1 Answers1

2

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.

Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152