Search code examples
pythonflaskuwsgi

How to stop an infinite loop when you run Flask application in a production mode


I have the Flask application. It has two buttons Start and Stop. A program should print 'pStart' (an infinite loop of pStart-s) when a user clicks Start and stop printing when a user clicks Stop. A user can resume printing when he clicks Start the second time.

from flask import Flask, render_template
import sys
flag1=True
app = Flask(__name__)

@app.route('/')
def index():
  return render_template('index.html')

@app.route('/start/')
def start():
  globals()['flag1']=True
  while flag1==True:
    print('pStart')
  return render_template('index.html')

@app.route('/stop/')
def stop():
  globals()['flag1']=False
  return render_template('index.html')

if __name__ == '__main__':
  app.run(host='0.0.0.0')

Here is my templates\index.html

<!doctype html>


<head>
    <title>Test</title> 
    <meta charset=utf-8>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

    </head>
    <body>
        <h1>My Website</h1>

<script type=text/javascript>
        $(function() {
          $('a#test').on('click', function(e) {
            e.preventDefault()
            $.getJSON('/start',
                function(data) {
              //do nothing
            });
            document.getElementById('btn1').disabled = true;
            document.getElementById('btn2').disabled = false;
            return false;
          });
        });
</script>

<script type=text/javascript>
        $(function() {
          $('a#test1').on('click', function(e) {
            e.preventDefault()
            $.getJSON('/stop',
                function(data) {
              //do nothing
            });
            document.getElementById('btn1').disabled = false;
            document.getElementById('btn2').disabled = true;
            return false;
          });
        });
</script>

<div class='container'>
        <form>
            <a href=# id=test><button id='btn1' class='btn btn-default'>Start</button></a>
        </form>
</div>  
<p>
<p>
<div class='container'>
        <form>
            <a href=# id=test1><button id='btn2' class='btn btn-default' type="button" disabled>Stop</button></a>
        </form>
</div>  
    </body>

This application works well in a development mode. However, when I run it with uWSGI I cannot stop it (stopless loop of print('pStart')). Here is my wsgi.py

from myproject import app

if __name__ == "__main__":
    app.run()

uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app

Update. The Flask application works if to use threads in code and enable threads "uwsgi --socket 0.0.0.0:5000 --protocol=http --enable-threads -w wsgi:app"

from flask import Flask, render_template
import threading
import time
from werkzeug.serving import make_server
import signal

app = Flask(__name__)
threads = []
stop_flags = []

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/start/')
def start():
    stop_flags.append(False)
    thread = threading.Thread(target=run_loop, args=(len(stop_flags) - 1,))
    threads.append(thread)
    thread.start()
    return render_template('index.html')

@app.route('/stop/')
def stop():
    stop_flags[-1] = True
    return render_template('index.html')

def run_loop(thread_id):
    while not stop_flags[thread_id]:
        print('pStart')
        time.sleep(0.5)

def signal_handler(signum, frame):
    for thread in threads:
        thread.join()
    server.shutdown()
    sys.exit(0)

if __name__ == '__main__':
    signal.signal(signal.SIGTERM, signal_handler)
    signal.signal(signal.SIGINT, signal_handler)
    server = make_server('0.0.0.0', 5000, app)
    server.serve_forever()

Solution

  • The while loop in the start function runs indefinitely and can not handle incoming requests. So when you run the application in production mode, the server is not able to handle any incoming requests after the start function is called !

    You can use a background task to run the print('pStart') continuously while allowing the Flask application to handle incoming requests, to do so use the threading modules.

    Here's how you can create the func in the background with threading :

      from flask import Flask, render_template
    import threading
    import time
    
    app = Flask(__name__)
    threads = []
    stop_flags = []
    
    @app.route('/')
    def index():
        return render_template('index.html')
    
    @app.route('/start/')
    def start():
        stop_flags.append(False)
        thread = threading.Thread(target=run_loop, args=(len(stop_flags) - 1,))
        threads.append(thread)
        thread.start()
        return render_template('index.html')
    
    @app.route('/stop/')
    def stop():
        stop_flags[-1] = True
        return render_template('index.html')
    
    def run_loop(thread_id):
        while not stop_flags[thread_id]:
            print('pStart')
            time.sleep(1)
    
    if __name__ == '__main__':
        app.run(debug=True)