pythonplotly-dashpython-multithreading

Stopping a dash server inside a thread does not work


I'm trying to stop a dash server within a thread, but it doesn't work:

Here's a minor example of the issue :

import threading
import requests
from dash import Dash, html
from flask import Flask, request
import logging
import time

# Setup Logger
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(threadName)s, %(levelname)s] %(message)s",
    handlers=[
        logging.StreamHandler()
    ]
)

# Create Flask server and associate it with the Dash app
flask_app = Flask(__name__)
app = Dash(__name__, server=flask_app)

# Define a simple layout
app.layout = html.Div(children=[
    html.H1(children='Hello Dash'),
    html.P(children='This is a simple Dash app for testing.'),
    html.Button('Click Me', id='button')
])

# Define a shutdown route
@app.server.route('/shutdown', methods=['POST'])
def shutdown():
    shutdown_server = request.environ.get('werkzeug.server.shutdown')
    if shutdown_server is None:
        raise RuntimeError('Not running with the Werkzeug Server')
    shutdown_server()
    return 'Server shutting down...'

# Function to run the server
def run_server():
    logging.info("Starting Dash server")
    app.run_server(debug=True, use_reloader=False)

# Function to stop the server
def stop_server():
    logging.info("Sending shutdown request")
    try:
        requests.post('http://127.0.0.1:8050/shutdown')
    except requests.RequestException as e:
        logging.error(f"Error sending shutdown request: {e}")
    logging.info("Joining server thread")
    server_thread.join()

# Start the server in a new thread
server_thread = threading.Thread(target=run_server)
server_thread.start()

# Start the server, wait a few seconds, then stop it
time.sleep(5)
stop_server()
logging.info("Server stopped")

When I run this script, the program hangs at server_thread.join() and does not exit. Using kill pid is not practical for me because I am running my program within a GUI application. Killing the pid would destroy the gui as well.


Solution

  • How about starting flask_app as a separate process instead of a thread. And then kill this process using pid.

    
    from dash import Dash, html
    from flask import Flask, request
    import logging
    import time
    from multiprocessing import Process, current_process
    import os
    
    # Setup Logger
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(threadName)s, %(levelname)s] %(message)s",
        handlers=[
            logging.StreamHandler()
        ]
    )
    
    # Create Flask server and associate it with the Dash app
    flask_app = Flask(__name__)
    app = Dash(__name__, server=flask_app)
    
    # Define a simple layout
    app.layout = html.Div(children=[
        html.H1(children='Hello Dash'),
        html.P(children='This is a simple Dash app for testing.'),
        html.Button('Click Me', id='button')
    ])
    
    
    # Define a shutdown route
    @app.server.route('/shutdown', methods=['POST'])
    def shutdown():
        proc = current_process()
    
    
    # Function to run the server
    def run_server():
        logging.info("Starting Dash server")
        app.run_server(debug=True, use_reloader=False)
    
    
    # Function to stop the server
    def stop_server(pid):
        logging.info("Sending shutdown request")
        os.kill(pid, 9)
        # requests.post('http://127.0.0.1:8050/shutdown')
        
    
    if __name__ == '__main__':    
        # Start the server in a new thread
        server_proc = Process(target=run_server, 
                                name='dash_server', 
                                daemon=True)
    
        server_proc.start()
        pid = server_proc.pid
        # Start the server, wait a few seconds, then stop it
        time.sleep(5)
        stop_server(pid)
        logging.info("Server stopped")
        time.sleep(30)
    

    To kill process on Windows try replacing with this lines:

    import signal
    os.kill(pid, signal.CTRL_C_EVENT)
    # or signal.CTRL_BREAK_EVENT