Runtime Error with Concurrent User Sessions - Models must be owned by only a single document

Hi, I’m deploying an application that uses Param and Panel to create an interactive visualization tool. However, I’m encountering the following error when multiple users open sessions concurrently and click the Submit button on a tab:

RuntimeError: Models must be owned by only a single document, ImportedStyleSheet(id=‘p1503’, …) is already in a doc

I’ve already tried the --num-procs approach, which helps to some extent, but I’m looking for a more robust solution. Specifically, I’d like to know:
• Is there a better approach to ensure user sessions remain isolated?
• Can this issue be addressed through code modifications rather than using --num-procs approach?

Here’s the link to a minimal reproducible example of the error:

The code in sample.py makes use of class Common in common.py and class Rectangle in rectangle.py to create the dashboard.

I have used the panel serve command to launch the application from the commandline.
Any insights or suggestions would be greatly appreciated. Thanks in advance!

Do you use pn.serve or obj.servable()

I’ve used obj.servable()

dynamically generating input widgets accordingly.

My guess is you need a clone somewhere, for whatever you’re returning dynamically, e.g. button.clone()

I tried that, unfortunately, that does not work.

The problem is that you declare (and share) button = Button() on your rectangle class.

Try changing the reactangle.py file to

import param
import panel as pn
import pandas as pd
from common import Common
import logging

class rectangle(param.Parameterized):
    length1 = param.Number(default=1, step=1, bounds=(1, 100))
    width1 = param.Number(default=1, step=1, bounds=(1, 100))
    length2 = param.Number(default=1, step=1, bounds=(1, 100))
    width2 = param.Number(default=1, step=1, bounds=(1, 100))
    submit = param.Event()
    common = param.ClassSelector(class_=Common)
    df = pd.DataFrame()

    def get_data(self):
        lengths = []
        widths = []
        perimeters = []
        areas = []

        for i in range(1, self.common.number_of_shapes + 1):
            lengths.append(getattr(self, f'length{i}'))
            widths.append(getattr(self, f'width{i}'))
            perimeters.append(2 * (lengths[i - 1] + widths[i - 1]))
            areas.append(lengths[i - 1] * widths[i - 1])

        self.df = pd.DataFrame({
            'Length': lengths,
            'Width': widths,
            'Perimeter': perimeters,
            'Area': areas
        })

    @param.depends('common.select_button')
    def get_first_dashboard(self):
        dimensions = pn.GridBox(ncols=self.common.number_of_shapes)
        for i in range(self.common.number_of_shapes):
            dimensions.append(
                pn.WidgetBox(
                    pn.Param(
                        self,
                        parameters=[f'length{i + 1}'],
                        widgets={
                            f'length{i + 1}': {
                                'widget_type': pn.widgets.IntInput,
                                'name': 'Length:',
                                'width': 115
                            }
                        },
                        show_name=False,
                        default_layout=pn.Column
                    ),
                    pn.Param(
                        self,
                        parameters=[f'width{i + 1}'],
                        widgets={
                            f'width{i + 1}': {
                                'widget_type': pn.widgets.FloatInput,
                                'name': 'Width:',
                                'width': 115
                            }
                        },
                        show_name=False,
                        default_layout=pn.Column
                    ),
                    margin=(4, 2, 4, 2)
                )
            )

        return dimensions

    def get_submit_button(self):
        return pn.widgets.Button.from_param(self.param.submit)

    @pn.depends('common.select_button', 'submit')
    def get_second_dashboard(self):
        self.get_data()
        logging.info("Done")
        return pn.Card(pn.widgets.Tabulator(self.df))

That works for me.

2 Likes

Thanks a lot, Marc. This seems to solve my issue. :smiley: