emilhe / dash-extensions

The dash-extensions package is a collection of utility functions, syntax extensions, and Dash components that aim to improve the Dash development experience
https://www.dash-extensions.com/
MIT License
409 stars 57 forks source link

Serverside + Backgroundmanager + Windows == Error #334

Open noxthot opened 2 weeks ago

noxthot commented 2 weeks ago

With recent versions, the example from https://www.dash-extensions.com/transforms/serverside_output_transform#a-serversideoutputtransform works nicely on both, Windows and Ubuntu.

A slight modification introducing a background-manager with Diskcache plus Progress bar breaks the app on Windows, while it is still running on Ubuntu.

The code which is not working (also check diff before):

import time
import plotly.express as px
import diskcache

from dash import DiskcacheManager
from dash_extensions.enrich import DashProxy, Output, Input, State, Serverside, html, dcc, \
    ServersideOutputTransform

cache = diskcache.Cache("./cache")

# Background callbacks require a cache manager
background_callback_manager = DiskcacheManager(cache)

app = DashProxy(transforms=[ServersideOutputTransform()], background_callback_manager=background_callback_manager)
app.layout = html.Div(
    [
        html.Button("Query data", id="btn"),
        dcc.Dropdown(id="dd"),
        html.P("0%", id="progress-component"),
        dcc.Graph(id="graph"),
        dcc.Store(id="store"),
    ]
)

@app.callback(
    Output("store", "data"),
    Input("btn", "n_clicks"),
    background=True,
    progress=[
        Output("progress-component", "children"),
    ],
    prevent_initial_call=True
)
def query_data(set_progress, _):
    total = 3

    for i in range(total):
        # Simulating a process step
        time.sleep(1)  # emulate slow database operation

        set_progress([(i + 1) / total])  # Update progress using set_progress
    return Serverside(px.data.gapminder())  # no JSON serialization here

@app.callback(Output("dd", "options"),  Output("dd", "value"), Input("store", "data"), prevent_initial_call=True)
def update_dd(df):
    options = [{"label": column, "value": column} for column in df["year"]]   # no JSON de-serialization here
    return options, options[0]['value']

@app.callback(Output("graph", "figure"), [Input("dd", "value"), State("store", "data")], prevent_initial_call=True)
def update_graph(value, df):
    df = df.query("year == {}".format(value))  # no JSON de-serialization here
    return px.sunburst(df, path=["continent", "country"], values="pop", color="lifeExp", hover_data=["iso_alpha"])

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

Installed and updated packages (for completeness; note, that this is from a "larger" project): https://pastebin.com/56Nitkmk

Stacktrace (Microsoft Windows; Version 23H2 (Build 22631.3737); this is a Windows 11 version)

PS C:\Users\anonymized\anonymizedproject> pdm run .\drafts\tmp\test_serverside.py
Dash is running on http://127.0.0.1:8050/

 * Serving Flask app 'test_serverside'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:8050
Press CTRL+C to quit
[2024-06-21 12:17:48,303] ERROR in app: Exception on /_dash-update-component [POST]
Traceback (most recent call last):
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\flask\app.py", line 1473, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\flask\app.py", line 882, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\flask\app.py", line 880, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\flask\app.py", line 865, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dash\dash.py", line 1373, in dispatch
    ctx.run(
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dash\_callback.py", line 389, in add_context
    job = callback_manager.call_job_fn(
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dash\long_callback\managers\diskcache_manager.py", line 126, in call_job_fn
    proc.start()
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\multiprocess\process.py", line 121, in start
    self._popen = self._Popen(self)
                  ^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\multiprocess\context.py", line 224, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\multiprocess\context.py", line 337, in _Popen
    return Popen(process_obj)
           ^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\multiprocess\popen_spawn_win32.py", line 95, in __init__
    reduction.dump(process_obj, to_child)
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\multiprocess\reduction.py", line 63, in dump
    ForkingPickler(file, protocol, *args, **kwds).dump(obj)
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 420, in dump
    StockPickler.dump(self, obj)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 481, in dump
    self.save(obj)
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 414, in save
    StockPickler.save(self, obj, save_persistent_id)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 597, in save
    self.save_reduce(obj=obj, *rv)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 711, in save_reduce
    save(state)
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 414, in save
    StockPickler.save(self, obj, save_persistent_id)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 554, in save
    f(self, obj)  # Call unbound method with explicit self
    ^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 1217, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 966, in save_dict
    self._batch_setitems(obj.items())
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 990, in _batch_setitems
    save(v)
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 414, in save
    StockPickler.save(self, obj, save_persistent_id)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 554, in save
    f(self, obj)  # Call unbound method with explicit self
    ^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 1985, in save_function
    _save_with_postproc(pickler, (_create_function, (
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 1117, in _save_with_postproc
    pickler.save_reduce(*reduction)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 686, in save_reduce
    save(args)
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 414, in save
    StockPickler.save(self, obj, save_persistent_id)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 554, in save
    f(self, obj)  # Call unbound method with explicit self
    ^^^^^^^^^^^^
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 881, in save_tuple
    save(element)
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 414, in save
    StockPickler.save(self, obj, save_persistent_id)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 554, in save
    f(self, obj)  # Call unbound method with explicit self
    ^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 1985, in save_function
    _save_with_postproc(pickler, (_create_function, (
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 1112, in _save_with_postproc
    pickler._batch_setitems(iter(source.items()))
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 990, in _batch_setitems
    save(v)
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 414, in save
    StockPickler.save(self, obj, save_persistent_id)
  File "C:\Users\anonymized\AppData\Local\Programs\Python\Python312\Lib\pickle.py", line 572, in save
    rv = reduce(self.proto)
         ^^^^^^^^^^^^^^^^^^
TypeError: cannot pickle '_contextvars.ContextVar' object
127.0.0.1 - - [21/Jun/2024 12:17:48] "POST /_dash-update-component HTTP/1.1" 500 -
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\multiprocess\spawn.py", line 122, in spawn_main
    exitcode = _main(fd, parent_sentinel)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\multiprocess\spawn.py", line 132, in _main
    self = reduction.pickle.load(from_parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 289, in load
    return Unpickler(file, ignore=ignore, **kwds).load()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\anonymized\anonymizedproject\.venv\Lib\site-packages\dill\_dill.py", line 444, in load
    obj = StockUnpickler.load(self)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
EOFError: Ran out of input

Please note that using DiskCache Manager, background manager and progress bar without ServerSide works without problem.

Any idea what is going on? Thanks!

noxthot commented 1 week ago

Update: 1.0.17 shows the same behaviour.

emilhe commented 1 week ago

I would suggest to use WSL, if you are on Windows. I believe that would solve the issue?

noxthot commented 1 week ago

I suppose this should work and this is actually our plan B, but I hoped to see the same behaviour with both OS.

I am personally using Ubuntu; this error occurs on another dev's Windows machine. Will keep you updated.

noxthot commented 59 minutes ago

fyi: works when using WSL