Different behavior of a Dash app depending on the use of the embedded web server or of gunicorn

Hi,

I am quite new to Dash.

I work on a Raspberry Pi cm5 running Trixie to develop a motion detection camera and also have the possibility of live streaming the camera data.

I use Dash 4.4.0 and Python 3.13.5. I also use Picamera2 with the CircularOutput2 feature. There is a PIR sensor generating an interrupt when detecting a movement which triggers the storing in a file of the video capturing the object that triggered the interrupt. This uses the CircularOutput2 feature of Picamera2.

There is a web app using Dash which can display the videos stored on the pi that were captured following a movement detection. The video is displayed in the webpage using DashPlayer.

In order to display the camera signal, an image of the camera is made available in jpeg format through a function using the yield capability of Python. This function uses the picamera2 function capture_file and is called every second (see code below).

def generate_frame():
    while True:
        stream = io.BytesIO()
        picam2.capture_file(stream, format="jpeg")
        jpeg_data = stream.getvalue()
        yield (
            b"--frame\r\n" + b"Content-Type: image/jpeg\r\n\r\n" + jpeg_data + b"\r\n"
        )
        time.sleep(1)

In the Dash app, the dash server calls the function above with the following combination of route and app_layout information:

@app.server.route("/video_feed)
def video_fedd():
    return Response(
        generate_frame(), mimetype="multipart/x-mixed-replace; boundary = frame"
    )

In the app_layout:

html.Img(src="/video_feed", style={'width': '60%', 'border': '2px solid #333'})

The application was developed using the non production web server started in main with:

app.run(host='0.0.0.0',debug=False, port=10033)

Where app is the Dash app.

The application works fine.

I wanted to replace the non production server with gunicorn. I ended up using the following approach to run gunicorn:

class StandaloneApplication(gunicorn.app.base.BaseApplication):

    def __init__(self, app, options=None):
        self.options = options or {}
        self.application = app
        super().__init__()

    def load_config(self):
        config = {key: value for key, value in self.options.items()
                  if key in self.cfg.settings and value is not None}
        for key, value in config.items():
            self.cfg.set(key.lower(), value)

    def load(self):
        return self.application
    
if __name__=='__main__':
    options = {
        'bind': '%s:%s' % ('0.0.0.0', '10033'),
        'workers': 2,
    }
    initialize()
    StandaloneApplication(server, options).run()

When using gunicorn, the initalization works well. However, no image is ever displayed in the webpage and after a few seconds, the application crashes stating that a worker might have gone out of memory.

After some investigation, it seems the function call picam2.capture_file(stream, format=“jpeg”) hangs with the gunicorn setup when it does not with the non production server.I am not sure why. Could you suggest an approach to investigate this issue and/or explain why I have this issue?

Thank you,

JF

Hi @jfh999 and welcome to the Dash community :slight_smile:

For a Dash app, you don’t normally need to subclass gunicorn.app.base.BaseApplication .

Here’s a more typical pattern:

from dash import Dash

app = Dash()
server = app.server

then Gunicorn can be started from the command line:

gunicorn app:server

or with more settings:

gunicorn --bind 0.0.0.0:10033 --workers 2 app:server