holoviz / panel

Panel: The powerful data exploration & web app framework for Python
https://panel.holoviz.org
BSD 3-Clause "New" or "Revised" License
4.63k stars 504 forks source link

Cannot display holoviews plots in lightning.ai #3643

Open MarcSkovMadsen opened 2 years ago

MarcSkovMadsen commented 2 years ago

Crosspost of https://github.com/Lightning-AI/lightning/issues/13381

pip install panel==0.13.1 bokeh==2.4.3 lightning==2022.6.15.post2 holoviews==1.14.9


I'm trying to create a Panel+HoloViz crossfiltering example with lightning.ai. I can create other examples. See https://github.com/Lightning-AI/lightning/issues/13335, but the HoloViews one never serves.

Example

app.py

# Source: https://github.com/Lightning-AI/lightning/blob/master/docs/source-app/workflows/build_lightning_app/from_scratch.rst
# Source: https://github.com/Lightning-AI/lightning/blob/master/src/lightning_app/frontend/stream_lit.py
from __future__ import annotations

import pathlib
from typing import Callable, Dict, Union

import holoviews as hv
import lightning_app as lapp
import panel as pn
from bokeh.sampledata import iris
from bokeh.server.server import Server
from lightning_app.core import constants

Page = Union[Callable[[], pn.viewable.Viewable], str, pathlib.Path]

def page_crossfilter():
    pn.extension(sizing_mode="stretch_width")
    hv.extension("bokeh")
    scatter = hv.Scatter(iris.flowers, kdims=["sepal_length"], vdims=["sepal_width"]).opts(responsive=True)

    return pn.pane.HoloViews(scatter, sizing_mode="stretch_both")

class LitPanelPage(lapp.LightningWork):
    """Can serve a single page Panel app"""

    def __init__(self, page: Page, **params):
        """_summary_

        Args:
            page (Callable[[], pn.viewable.Viewable]): A function returning a Panel `Viewable`.
                Alternatively a path to a file containing a Panel application.
        """
        if isinstance(page, pathlib.Path):
            page = str(page)
        self._page = page

        self._server: Union[None, Server] = None

        super().__init__(**params)

    def run(self):
        """Starts the server and serves the page"""
        self._server = pn.serve(
            {"/": self._page},
            port=self.port,
            address=self.host,
            websocket_origin=self.websocket_origin,
            show=False,
        )

    def stop(self):
        """Stops the server"""
        if self._server:
            self._server.stop()

    def get_tab(self, name: str) -> Dict[str, str | "LitPanelPage"]:
        """Returns a *tab* definition to be included in the layout of a LightingFlow"""
        return {"name": name, "content": self}

    @property
    def websocket_origin(self) -> str:
        host = constants.APP_SERVER_HOST.replace("http://", "").replace("https://", "")
        return host + ":" + str(self.port)

class LitApp(lapp.LightningFlow):
    def __init__(self):
        super().__init__()
        self.lit_crossfilter = LitPanelPage(page=page_crossfilter, parallel=True)

    def run(self):
        self.lit_crossfilter.run()

    def configure_layout(self):
        return [
            self.lit_crossfilter.get_tab(name="Crossfiltering"),
        ]

if __name__ == "__main__":
    # lightning run app app.py
    app = lapp.LightningApp(LitApp())
elif __name__.startswith("bokeh"):
    # panel serve app.py --autoreload --show
    page_crossfilter().servable()

run with

$ lightning run app app.py
Your Lightning App is starting. This won't take long.
INFO: Your app has started. View it in your browser: http://127.0.0.1:7501/view
Launching server at http://127.0.0.1:62458

And see it loads forever

image

If instead I do

$ panel serve app.py
2022-06-23 07:33:25,018 Starting Bokeh server version 2.4.3 (running on Tornado 6.1)
2022-06-23 07:33:25,019 User authentication hooks NOT provided (default user enabled)
2022-06-23 07:33:25,026 Bokeh app running at: http://localhost:5006/app
2022-06-23 07:33:25,026 Starting Bokeh server with process id: 31292
2022-06-23 07:33:29,603 WebSocket connection opened
2022-06-23 07:33:29,603 ServerConnection created

It works

image

MarcSkovMadsen commented 2 years ago

My hypothesis is that there is some kind of race condition or that holoviews is not thread safe ????

MarcSkovMadsen commented 2 years ago

The issue seems to be that the Panel server is too slow to respond to the initial request from the lightning.ai server.

My hypothesis is that the lightning.ai server sends initial requests to the Panel server to figure out when its a live. It waits for a small period of time and then sends another request. So I just see an infinite number of requests coming in. My experiments have also previously shown that Panel/ Bokeh server is slower than other data app frameworks to send the initial response back. Partly because it sends a large response. But probably also because its not been optimized for this use case.

See the cross post for more info https://github.com/Lightning-AI/lightning/issues/13381 for more details.