Dash multiple sessions - not sharing variables

Hi,

I have tried to find a solution in the docs and the forum without luck, although it should be something I would expect a typical use case, so maybe I am not framing it right. I built a Dash application and deployed on Azure, using gunicorn with 1 up to 4 workers (I tried both), intended for users to label data, store labels and upload to a SQL DB. Multiple users may be logged in and label simultaneously. However the variables are shared between them which creates a messy situation where one thinks he’s labeling one dataset although it is the other session’s dataset he’s adding labels to.
I thought only global variables were shared, but I have not declared any variable global. Can anyone explain why variables are updated similarly across all clients?
Sorry for possibly bad wording, quite new to Dash!

1 Like

Could you add some example code? Without it, it is hard to comment on, what goes wrong.

Sure, trying to give a small reproduceable piece:

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.CERULEAN], title='MyApp')
server = app.server
app.config.suppress_callback_exceptions = True


def serve_layout():
    return html.Div([
        dcc.Location(id='url'),
        html.Div(id='page-content')
    ])


from myapp import MyApp
app.layout = serve_layout
t = MyApp()

@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/time-series':
        return t.app()


@app.callback(
    [Output("popover", "is_open")],
    [Input("popover-target", "n_clicks")]
)
def combined_popover(n_click_popover, n_click_submit, close_select_click, close_modal_click, close_interval_warning,
                     is_open, relayout_data, radioitem_value, tags_dropdown_value):

    new_tag = tag.Tag(tag_type, tag_class, start_date, end_date)
    t.add_tag(t.sensor_id, new_tag)

In MyApp class:

    def app(self):

        layout = html.Div([
            dbc.Row(dbc.Col(self.nav)),
            dbc.Row(dbc.Col(self.header)),
            dbc.Row(dbc.Col(dcc.Loading(id='loading-graph', children=[self.graphs, self.graph],
                                        type='circle'))),
            dbc.Row([dbc.Col(self.tags), dbc.Col(self.alerts_card)])
        ], style={'padding': 10})
        return layout

    def add_tag(self, sensor_id, tag):
        v_id = self.reference[self.reference.SensorId == sensor_id].VId.values[0]
        position = self.reference[self.reference.SensorId == sensor_id].Position.values[0]
        data = {'SensorId': sensor_id, 'EventStartDateTime': tag.start, 'EventEndDateTime': tag.end,
                'EventType': tag.tag_type, 'VId': vid, 'Position': position,
                'Class': tag.class}
        self.tags_pd = self.tags_pd.append(data, ignore_index=True)

So eventually all class members for the object t are shared among all clients, and in this example for instance tags_pd is the same for all clients, although I would like them to be independent between sessions/users. Does this explain a bit better?

Yes, that explains it perfectly. As you declare the object t in the module scope, it will essentially be a global variable. Which results in shared state between clients (best case) or simply inconsistent behavior depending on the deployment setup. This is why this approach is not recommended.

Instead, you should store the state on the page, e.g. in a Store element. This will yield independent state for each client, and a consistent behavior across different deployment setups.

1 Like

I see, I didn’t think that would make it a global variable. As far as I understand, the Store element is meant to store JSON data. In my case, the object t of class MyApp has a lot of members, including pandas. Is there a way to declare the variable non global, or how would the Store component work, I’m struggling to understand how to use it? I suppose I cannot just wrap it like this :

def serve_layout():
    return html.Div([
        dcc.Location(id='url'),
        html.Div(children=[dcc.Store(id='page-content', storage_type='session')])
    ])

app.layout = serve_layout
t = MyApp()
@app.callback(Output('page-content', 'data'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/time-series':
        return t.app()

No, that is the same :wink: . A key design principle of Dash is to keep the backend (i.e. the Python code) stateless, i.e. you should not have any variables in the module scope (unless they are read only, that’s okay).

If you need to share the object t between callbacks, you should save it to a Store component (or similar). And yes, that requires making the object JSON serializeable. There are other options (e.g. server side caching), but I think that you should consider revising the design of you MyApp in any case.

1 Like

Ah, in this case I will rewrite the app so no variable is in the main module. Thank you for the help!

Hi jokin,
I am currently struggling with the same problem you once had some time ago. Did you rewrite your app as suggested by Emil? Could you share some lessons learned?
If I would create a class object within a callback, would this lead also to a stateful situation? As I understood, global scope on module level is the problem. But instantiating in callback too?

Hey Chris, yes, I did rewrite the app and now using Store elements as Emil suggested, and that solved it.