Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flask: Background thread sees a non-empty queue as empty

When I run a Flask app in uwsgi, the background thread and the app functions see different values when querying size of the same Queue.

Components

  • A Flask application with a thread-safe queue.
  • A GET call returns the queue size.
  • A POST call adds an element to the Queue.
  • A background thread prints the Queue size

The problem

When the app is from the shell using python tester.py, I get the expected result:

2014-06-07 14:20:50.677995 Queue size is: 0
127.0.0.1 - - [07/Jun/2014 14:20:51] "POST /addMessage/X HTTP/1.1" 200 -
2014-06-07 14:20:51.679277 Queue size is: 1
2014-06-07 14:20:52.680425 Queue size is: 1
2014-06-07 14:20:53.681566 Queue size is: 1
2014-06-07 14:20:54.682708 Queue size is: 1
127.0.0.1 - - [07/Jun/2014 14:20:55] "POST /addMessage/Y HTTP/1.1" 200 -
2014-06-07 14:20:55.687755 Queue size is: 2
2014-06-07 14:20:56.688867 Queue size is: 2

However, when the app is executed using uwsgi, I get the following in the logs:

2014-06-07 14:17:42.056863 Queue size is: 0
2014-06-07 14:17:43.057952 Queue size is: 0
[pid: 9879|app: 0|req: 6/6] 127.0.0.1 () {24 vars in 280 bytes} [Sat Jun  7 14:17:43 2014] POST /addMessage/X => generated 16 bytes in 0 msecs (HTTP/1.1 200) 2 headers in 71 bytes (1 switches on core 0)
2014-06-07 14:17:44.059037 Queue size is: 0
2014-06-07 14:17:45.060118 Queue size is: 0
[pid: 9879|app: 0|req: 7/7] 127.0.0.1 () {24 vars in 280 bytes} [Sat Jun  7 14:17:45 2014] POST /addMessage/X => generated 16 bytes in 0 msecs (HTTP/1.1 200) 2 headers in 71 bytes (1 switches on core 0)
2014-06-07 14:17:46.061205 Queue size is: 0
2014-06-07 14:17:47.062286 Queue size is: 0

When running under uwsgi, the background thread does not see the same queue as the app. Why is that? How can I make these two threads look at the same Queue object?

Updates

  • I see inconsistent behaviour even when it's executed as a Python script: Sometimes it does not manage to log messages (using app.logger), and I can only see prints. This means that the thread is running, but it can't do anything with app.logger.

uwsgi .ini configuration

[uwsgi]
http-socket    = :9002
plugin         = python
wsgi-file      = /home/ubuntu/threadtest-uwsgi.py
enable-threads = true
workers        = 1
chdir          = /home/ubuntu/thread-tester/thread_tester

Code

from flask import Flask, jsonify
import Queue
from threading import Thread
import time
import datetime
import logging
import sys

logging.basicConfig(stream=sys.stderr,
                    format='%(asctime)s %(levelname)s - %(message)s')

app = Flask(__name__)
messages = Queue.Queue()

def print_queue_size():
    while True:
        app.logger.debug("%s Queue size is: %d" % (datetime.datetime.now(),
                                        messages.qsize()))
        time.sleep(1)

t = Thread(target=print_queue_size, args=())
t.setDaemon(True)
t.start()

@app.route("/queueSize", methods=["GET"])
def get_queue_size():
    return jsonify({"qsize": messages.qsize()}), 200

@app.route("/addMessage/<message>", methods=["POST"])
def add_message_to_queue(message):
    messages.put(message)
    return jsonify({"qsize": messages.qsize()}), 200

if __name__ == "__main__":
    app.run(port=6000)
like image 433
Adam Matan Avatar asked Jun 07 '14 14:06

Adam Matan


2 Answers

From the Things to Know documenation page:

uWSGI tries to (ab)use the Copy On Write semantics of the fork() call whenever possible. By default it will fork after having loaded your applications to share as much of their memory as possible. If this behavior is undesirable for some reason, use the lazy option. This will instruct uWSGI to load the applications after each worker’s fork(). Lazy mode changes the way graceful reloading works: instead of reloading the whole instance, each worker is reloaded in chain. If you want “lazy app loading”, but want to maintain the standard uWSGI reloading behaviour, starting from 1.3 you can use the lazy-apps option.

Your Flask app is started when uWSGI starts, then the one worker process is forked. On forking, the Queue object is empty, and no longer shared with the original process. The thread isn't taken along.

Try setting the lazy-apps option to delay the loading of the Flask app until the worker is started.

like image 99
Martijn Pieters Avatar answered Nov 17 '22 21:11

Martijn Pieters


The documentation link in @Martijn Pieters' answer notes that lazy-apps may consume more memory than preforking. If you're concerned about this, you may also wish to consider the @postfork decorator to have more granular control over what gets run after forking. You could create your Queue inside a @postfork-decorated function and it will get created in each worker.

like image 41
Brian from QuantRocket Avatar answered Nov 17 '22 22:11

Brian from QuantRocket