How to integrate dash with another process that might be blocking

That’s what I would recommend doing. Create a semaphore and write it to the disk. IO is great because it works across processes. Here are some things to consider (all of which should be handled by using IO):

  • What happens when multiple people visit the dash app to control the hardware? When reading from a file, each new session will get its “source of truth” from the file.
  • What happens when you deploy across multiple processes? Right now, a single callback / request can block the entire process (preventing any other callbacks or requests from firing). To solve this, you’ll need to run this app on multiple processes/threads with something like $ gunicorn app:server --workers 4 --threads 2. In this case, the memory won’t be shared across processes which is why modifying global variables isn’t safe. However, reading files is.

Here’s a simple example with a file lock:

import dash
from dash.dependencies import Input, Output, Event
import dash_core_components as dcc
import dash_html_components as html

import datetime
import time


class Semaphore:
    def __init__(self, filename='semaphore.txt'):
        self.filename = filename
        with open(self.filename, 'w') as f:
            f.write('done')

    def lock(self):
        with open(self.filename, 'w') as f:
            f.write('working')

    def unlock(self):
        with open(self.filename, 'w') as f:
            f.write('done')

    def is_locked(self):
        return open(self.filename, 'r').read() == 'working'


semaphore = Semaphore()


def long_process():
    if semaphore.is_locked():
        raise Exception('Resource is locked')
    semaphore.lock()
    time.sleep(7)
    semaphore.unlock()
    return datetime.datetime.now()


app = dash.Dash()
server = app.server


def layout():
    return html.Div([
        html.Button('Run Process', id='button'),
        dcc.Interval(id='interval', interval=500),
        html.Div(id='lock'),
        html.Div(id='output'),
    ])


app.layout = layout


@app.callback(
    Output('lock', 'children'),
    events=[Event('interval', 'interval')])
def display_status():
    return 'Running...' if semaphore.is_locked() else 'Free'


@app.callback(
    Output('output', 'children'),
    events=[Event('button', 'click')])
def run_process():
    return 'Finished at {}'.format(long_process())


if __name__ == '__main__':
    app.run_server(debug=True, processes=5)

Let me know if that helps!

1 Like