How to use pn.state.busy in multiple sessions

I want to incorporate a loading indicator in my app at a custom location, as per the documentation, I tried to use pn.state.busy to determine busyness state. However, when I open the app from a different session, I get the following error

RuntimeError: Models must be owned by only a single document, ImportedStyleSheet(id='p1172', ...) is already in a doc

I understand that this is because pn.state object is shared across session, I am not sure how to avoid this, minimal reproducer, taken from the Bootstrap Template example here -

import hvplot.pandas
import numpy as np
import panel as pn
import pandas as pd
import param

class SampleApp(pn.viewable.Viewer):

    loading = pn.indicators.LoadingSpinner(value=True, size=25, styles={'padding-top': '8px'})

    def busy_indicator(self, busy):
        return pn.WidgetBox(self.loading, styles={'padding-top': '7px', 'border': 'none'}) \
            if busy else None

    def get_template(self):
        xs = np.linspace(0, np.pi)
        
        freq = pn.widgets.FloatSlider(name="Frequency", start=0, end=10, value=2)
        phase = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)
        
        def sine(freq, phase):
            return pd.DataFrame(dict(y=np.sin(xs*freq+phase)), index=xs)
        
        def cosine(freq, phase):
            return pd.DataFrame(dict(y=np.cos(xs*freq+phase)), index=xs)
        
        dfi_sine = hvplot.bind(sine, freq, phase).interactive()
        dfi_cosine = hvplot.bind(cosine, freq, phase).interactive()
        
        plot_opts = dict(responsive=True, min_height=400)
        
        # Instantiate the template with widgets displayed in the sidebar
        template = pn.template.BootstrapTemplate(
            title='BootstrapTemplate',
            sidebar=[freq, phase],
        )
        # Append a layout to the main area, to demonstrate the list-like API
        template.main.append(
            pn.Row(
                pn.bind(self.busy_indicator, pn.state.param.busy), # Binding the busy state param to show a loading indicator
                pn.Card(dfi_sine.hvplot(**plot_opts).output(), title='Sine'),
                pn.Card(dfi_cosine.hvplot(**plot_opts).output(), title='Cosine'),
            )
        )
        return template

    def __init__(self):
        self.template = self.get_template()


if __name__.startswith("bokeh"):
    SampleApp().template.servable()

I am running the above script using -

panel serve sample_app.py

The above error occurs when I open 2 parallel instances of the app. I also tried using pn.state.sync_busy instead of pn.bind, but it results in the same issue. I even tried binding the busy_indicator function to bootstrap template’s busy indicator, but I get the same error, what should be the apporpriate way to handle this?

Kind of sounds like a bug, but I’m not certain. Can you raise an issue on GitHub?

Actually, I think you can maybe do

sidebar = pn.WidgetBox(freq, phase, loading=pn.state.param.busy)
template = pn.template.BootstrapTemplate(
            title='BootstrapTemplate',
            sidebar=[sidebar],
        )

1 Like

Thanks @ahuang11 , I realize that pn.state.busy is actually shared across sessions, is there a way to get the busy state specific to the current session?

Hi @AnJ10

pn.state.busy is to my understanding not shared across sessions.

The problems are that

  • you define the loading spinner as a class variable and that
  • you forget to run super().__init__ in your __init__ function.

This works

import hvplot.pandas
import numpy as np
import panel as pn
import pandas as pd
import param

class SampleApp(pn.viewable.Viewer):

    def busy_indicator(self, busy):
        return pn.WidgetBox(self.loading, styles={'padding-top': '7px', 'border': 'none'}) if busy else None

    def get_template(self):
        xs = np.linspace(0, np.pi)
        
        freq = pn.widgets.FloatSlider(name="Frequency", start=0, end=10, value=2)
        phase = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)
        
        def sine(freq, phase):
            return pd.DataFrame(dict(y=np.sin(xs*freq+phase)), index=xs)
        
        def cosine(freq, phase):
            return pd.DataFrame(dict(y=np.cos(xs*freq+phase)), index=xs)
        
        dfi_sine = hvplot.bind(sine, freq, phase).interactive()
        dfi_cosine = hvplot.bind(cosine, freq, phase).interactive()
        
        plot_opts = dict(responsive=True, min_height=400)
        
        # Instantiate the template with widgets displayed in the sidebar
        template = pn.template.BootstrapTemplate(
            title='BootstrapTemplate',
            sidebar=[freq, phase],
        )
        # Append a layout to the main area, to demonstrate the list-like API
        template.main.append(
            pn.Row(
                pn.bind(self.busy_indicator, pn.state.param.busy), # Binding the busy state param to show a loading indicator
                pn.Card(dfi_sine.hvplot(**plot_opts).output(), title='Sine'),
                pn.Card(dfi_cosine.hvplot(**plot_opts).output(), title='Cosine'),
            )
        )
        return template

    def __init__(self):
        super().__init__()
        self.loading = pn.indicators.LoadingSpinner(value=True, size=25, styles={'padding-top': '8px'})
        self.template = self.get_template()


if __name__.startswith("bokeh"):
    SampleApp().template.servable()

Thanks @Marc !

This helped me to get rid of the error. However I still believe that pn.state.busy is shared across sessions, If I open 2 separate windows of the app that you shared above, and do something in one window that makes the session busy (Toggle the value in the slider), the loading indicator also appears in the other window. This is also True for the default loading indicator that is provided by the bootstrap template, which also internally references pn.state.sync_busy.

Looking at the code here, state is defined when module is imported so it does make sense that pn.state is shared across sessions, although for session specific stuff we do have pn.state.session_args, but I don’t think it has anything to identify the busyness of the session.

I suspect that you might have some things that are blocking the main thread (check out Visual indicator to check if app is blocking main thread to test). To get around that you might need to set --nprocs 2 or something similar.