posit-dev / py-shiny

Shiny for Python
https://shiny.posit.co/py/
MIT License
1.26k stars 75 forks source link

Pulse busy indicator ends prematurely even when the plot hasn't finished loading #1379

Closed karangattu closed 5 months ago

karangattu commented 5 months ago

When using several plots in the shiny app and busy_indicators.use(spinners=False) the pulse shows up but ends prematurely even before the plots have finished loading after the intentional sleep. See Jam link for recording.

Jam link - https://jam.dev/c/6050cd56-f464-4986-81a8-f1dfdd168f8a


App code:

# pyright:basic
import time

import numpy as np
import seaborn as sns

from shiny import App, module, reactive, render, ui

# -- Reusable card module --
@module.ui
def card_ui(spinner_type, spinner_color, spinner_size):
    return ui.card(
        # ui.busy_indicators.options(
        #     spinner_type=spinner_type,
        #     spinner_color=spinner_color,
        #     spinner_size=spinner_size,
        # ),
        ui.card_header("Spinner: " + spinner_type),
        ui.output_plot("plot"),
    )

@module.server
def card_server(input, output, session, rerender):
    @render.plot
    def plot():
        rerender()
        time.sleep(2)
        sns.lineplot(x=np.arange(100), y=np.random.randn(100))

# -- Main app --
app_ui = ui.page_fillable(
    ui.busy_indicators.options(
        pulse_background="linear-gradient(45deg, blue, red)",
        pulse_height="100px",
        pulse_speed="4s",
    ),
    ui.busy_indicators.use(pulse=True, spinners=False),
    ui.input_task_button("rerender", "Re-render"),
    ui.layout_columns(
        card_ui("ring", "ring", "red", "10px"),
        card_ui("bars", "bars", "green", "20px"),
        card_ui("dots", "dots", "blue", "30px"),
        card_ui("pulse", "pulse", "olive", "50px"),
        col_widths=6,
    ),
)

def server(input, output, session):
    time.sleep(3)

    @reactive.calc
    def rerender():
        return input.rerender()

    card_server("ring", rerender=rerender)
    card_server("bars", rerender=rerender)
    card_server("dots", rerender=rerender)
    card_server("pulse", rerender=rerender)

app = App(app_ui, server, debug=True)

logs when debug option is turned on for local shiny

INFO:     127.0.0.1:50336 - "GET /?vscodeBrowserReqId=1715719900005 HTTP/1.1" 200 OK
SEND: {"progress": {"type": "binding", "message": {"id": "ring-plot"}}}
SEND: {"progress": {"type": "binding", "message": {"id": "bars-plot"}}}
SEND: {"progress": {"type": "binding", "message": {"id": "dots-plot"}}}
SEND: {"progress": {"type": "binding", "message": {"id": "pulse-plot"}}}
remove_session: e72d51e26811a74e5e86f358097eca1b9f7f8efe42c49c4c858e4834b535372f
INFO:     connection closed
INFO:     ('127.0.0.1', 50339) - "WebSocket /websocket/" [accepted]
SEND: {"config": {"workerId": "", "sessionId": "66d8b9c5ab5f035f1fe58cdd4400a997fed2b1723fc721ddef05bb1ff12539f0", "user": null}}
INFO:     connection open
RECV: {"method":"init","data":{"rerender:bslib.taskbutton":{"value":0,"autoReset":true},".clientdata_output_ring-plot_width":278,".clientdata_output_ring-plot_height":141.5,".clientdata_output_bars-plot_width":278,".clientdata_output_bars-plot_height":141.5,".clientdata_output_dots-plot_width":278,".clientdata_output_dots-plot_height":141.5,".clientdata_output_pulse-plot_width":278,".clientdata_output_pulse-plot_height":141.5,".clientdata_output_ring-plot_bg":"rgb(255, 255, 255)",".clientdata_output_ring-plot_fg":"rgb(29, 31, 33)",".clientdata_output_ring-plot_accent":"rgb(0, 123, 194)",".clientdata_output_ring-plot_font":{"families":["Open Sans","-apple-system","system-ui","Segoe UI","Roboto","Helvetica Neue","Arial","sans-serif"],"size":"16px"},".clientdata_output_bars-plot_bg":"rgb(255, 255, 255)",".clientdata_output_bars-plot_fg":"rgb(29, 31, 33)",".clientdata_output_bars-plot_accent":"rgb(0, 123, 194)",".clientdata_output_bars-plot_font":{"families":["Open Sans","-apple-system","system-ui","Segoe UI","Roboto","Helvetica Neue","Arial","sans-serif"],"size":"16px"},".clientdata_output_dots-plot_bg":"rgb(255, 255, 255)",".clientdata_output_dots-plot_fg":"rgb(29, 31, 33)",".clientdata_output_dots-plot_accent":"rgb(0, 123, 194)",".clientdata_output_dots-plot_font":{"families":["Open Sans","-apple-system","system-ui","Segoe UI","Roboto","Helvetica Neue","Arial","sans-serif"],"size":"16px"},".clientdata_output_pulse-plot_bg":"rgb(255, 255, 255)",".clientdata_output_pulse-plot_fg":"rgb(29, 31, 33)",".clientdata_output_pulse-plot_accent":"rgb(0, 123, 194)",".clientdata_output_pulse-plot_font":{"families":["Open Sans","-apple-system","system-ui","Segoe UI","Roboto","Helvetica Neue","Arial","sans-serif"],"size":"16px"},".clientdata_output_ring-plot_hidden":false,".clientdata_output_bars-plot_hidden":false,".clientdata_output_dots-plot_hidden":false,".clientdata_output_pulse-plot_hidden":false,".clientdata_pixelratio":2,".clientdata_url_protocol":"http:",".clientdata_url_hostname":"localhost",".clientdata_url_port":"62542",".clientdata_url_pathname":"/",".clientdata_url_search":"?vscodeBrowserReqId=1715719900005",".clientdata_url_hash_initial":"",".clientdata_url_hash":"",".clientdata_singletons":""}}
SEND: {"busy": "busy"}
SEND: {"busy": "busy"}
SEND: {"busy": "busy"}
SEND: {"busy": "busy"}
SEND: {"recalculating": {"name": "ring-plot", "status": "recalculating"}}
/Users/karangathani/.pyenv/versions/3.10.12/lib/python3.10/site-packages/seaborn/_oldcore.py:1119: FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead.
  with pd.option_context('mode.use_inf_as_na', True):
/Users/karangathani/.pyenv/versions/3.10.12/lib/python3.10/site-packages/seaborn/_oldcore.py:1119: FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead.
  with pd.option_context('mode.use_inf_as_na', True):
SEND: {"recalculating": {"name": "ring-plot", "status": "recalculated"}}
SEND: {"busy": "idle"}
SEND: {"recalculating": {"name": "bars-plot", "status": "recalculating"}}
/Users/karangathani/.pyenv/versions/3.10.12/lib/python3.10/site-packages/seaborn/_oldcore.py:1119: FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead.
  with pd.option_context('mode.use_inf_as_na', True):
/Users/karangathani/.pyenv/versions/3.10.12/lib/python3.10/site-packages/seaborn/_oldcore.py:1119: FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead.
  with pd.option_context('mode.use_inf_as_na', True):
SEND: {"recalculating": {"name": "bars-plot", "status": "recalculated"}}
SEND: {"busy": "idle"}
SEND: {"recalculating": {"name": "dots-plot", "status": "recalculating"}}
/Users/karangathani/.pyenv/versions/3.10.12/lib/python3.10/site-packages/seaborn/_oldcore.py:1119: FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead.
  with pd.option_context('mode.use_inf_as_na', True):
/Users/karangathani/.pyenv/versions/3.10.12/lib/python3.10/site-packages/seaborn/_oldcore.py:1119: FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead.
  with pd.option_context('mode.use_inf_as_na', True):
SEND: {"recalculating": {"name": "dots-plot", "status": "recalculated"}}
SEND: {"busy": "idle"}
SEND: {"recalculating": {"name": "pulse-plot", "status": "recalculating"}}
/Users/karangathani/.pyenv/versions/3.10.12/lib/python3.10/site-packages/seaborn/_oldcore.py:1119: FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead.
  with pd.option_context('mode.use_inf_as_na', True):
/Users/karangathani/.pyenv/versions/3.10.12/lib/python3.10/site-packages/seaborn/_oldcore.py:1119: FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead.
  with pd.option_context('mode.use_inf_as_na', True):
SEND: {"recalculating": {"name": "pulse-plot", "status": "recalculated"}}
SEND: {"busy": "idle"}
SEND: {"values": {}, "inputMessages": [], "errors": {}}
SEND: {"values": {"ring-plot": {"src": "data:image/png;[base64 data]", "width": "100%", "height": "100%", "coordmap": {"panels": [{"panel": 1, "row": 1, "col": 1, "domain": {"left": -4.95, "right": 103.95, "bottom": -2.4214277288043156, "top": 3.8023283645915047}, "range": {"left": 93.81944444444444, "right": 515.0670224977042, "bottom": 204.55555555555554, "top": 30.0}, "log": {"x": null, "y": null}, "mapping": {"x": null, "y": null}}], "dims": {"width": 556.0, "height": 282.0}}}, "bars-plot": {"src": "data:image/png;[base64 data]", "width": "100%", "height": "100%", "coordmap": {"panels": [{"panel": 1, "row": 1, "col": 1, "domain": {"left": -4.95, "right": 103.95, "bottom": -3.3588246873175867, "top": 2.6127483937494302}, "range": {"left": 90.31944444444444, "right": 515.0670224977042, "bottom": 204.55555555555551, "top": 30.0}, "log": {"x": null, "y": null}, "mapping": {"x": null, "y": null}}], "dims": {"width": 556.0, "height": 282.0}}}, "dots-plot": {"src": "data:image/png;[base64 data]", "width": "100%", "height": "100%", "coordmap": {"panels": [{"panel": 1, "row": 1, "col": 1, "domain": {"left": -4.95, "right": 103.95, "bottom": -2.725607443797542, "top": 2.4372076220702676}, "range": {"left": 90.31944444444444, "right": 515.0670224977042, "bottom": 204.55555555555554, "top": 30.000000000000057}, "log": {"x": null, "y": null}, "mapping": {"x": null, "y": null}}], "dims": {"width": 556.0, "height": 282.0}}}, "pulse-plot": {"src": "data:image/png;[base64 data]", "width": "100%", "height": "100%", "coordmap": {"panels": [{"panel": 1, "row": 1, "col": 1, "domain": {"left": -4.95, "right": 103.95, "bottom": -3.2518020045696256, "top": 2.567634079791348}, "range": {"left": 90.31944444444444, "right": 515.0670224977042, "bottom": 204.55555555555554, "top": 30.0}, "log": {"x": null, "y": null}, "mapping": {"x": null, "y": null}}], "dims": {"width": 556.0, "height": 282.0}}}}, "inputMessages": [{"id": "rerender", "message": {"state": "ready"}}], "errors": {}}
gadenbuie commented 5 months ago

I think the pulse busy indicator is behaving as expected, but the server state messages are unexpected. The important part of the message logs is below. Notice that we receive the {"busy": "idle"} message several times between "recalculating" events and before we receive that "values" event that completes the reactive cycle.

SEND: {"busy": "busy"}
SEND: {"busy": "busy"}
SEND: {"busy": "busy"}
SEND: {"busy": "busy"}
SEND: {"recalculating": {"name": "ring-plot", "status": "recalculating"}}
SEND: {"recalculating": {"name": "ring-plot", "status": "recalculated"}}
SEND: {"busy": "idle"}
SEND: {"recalculating": {"name": "bars-plot", "status": "recalculating"}}
SEND: {"recalculating": {"name": "bars-plot", "status": "recalculated"}}
SEND: {"busy": "idle"}
SEND: {"recalculating": {"name": "dots-plot", "status": "recalculating"}}
SEND: {"recalculating": {"name": "dots-plot", "status": "recalculated"}}
SEND: {"busy": "idle"}
SEND: {"recalculating": {"name": "pulse-plot", "status": "recalculating"}}
SEND: {"recalculating": {"name": "pulse-plot", "status": "recalculated"}}
SEND: {"busy": "idle"}
SEND: {"values": {"ring-plot": {...}, "dots-plot": {...}, "pulse-plot": {...}, "inputMessages": [{"id": "rerender", "message": {"state": "ready"}}], "errors": {}}
gadenbuie commented 5 months ago

I created a similar app in Shiny for R where the messages logs are consistent with what we'd expect

SEND {"busy":"busy"}
SEND {"recalculating":{"name":"ring-plot","status":"recalculating"}}
SEND {"recalculating":{"name":"ring-plot","status":"recalculated"}}
SEND {"recalculating":{"name":"bars-plot","status":"recalculating"}}
SEND {"recalculating":{"name":"bars-plot","status":"recalculated"}}
SEND {"recalculating":{"name":"dots-plot","status":"recalculating"}}
SEND {"recalculating":{"name":"dots-plot","status":"recalculated"}}
SEND {"recalculating":{"name":"pulse-plot","status":"recalculating"}}
SEND {"recalculating":{"name":"pulse-plot","status":"recalculated"}}
SEND {"busy":"idle"}
SEND {"errors":{},"values":{"pulse-plot":{...},"dots-plot":{...},"bars-plot":{...},"ring-plot":{...}},"inputMessages":[]}

Notice that there's a single {"busy": "idle"} message just before the {"values"} message.

Shiny for R App ```r library(shiny) library(bslib) options(shiny.trace = TRUE) # Reusable card module card_ui <- function(id, spinner_type = id) { card( card_header("Spinner:", spinner_type), plotOutput(shiny::NS(id, "plot")) ) } card_server <- function(id, rerender) { moduleServer(id, function(input, output, session) { output$plot <- renderPlot({ rerender() Sys.sleep(2) plot(x = 1:100, cumsum(rnorm(100))) }) }) } # Main app ui <- page_fillable( useBusyIndicators(pulse = TRUE, spinners = FALSE), actionButton("rerender", "Rerender"), fluidRow( column(width = 6, card_ui("ring")), column(width = 6, card_ui("bars")), column(width = 6, card_ui("dots")), column(width = 6, card_ui("pulse")) ) ) server <- function(input, output, session) { rerender <- reactive({ input$rerender }) card_server("ring", rerender = rerender) card_server("bars", rerender = rerender) card_server("dots", rerender = rerender) card_server("pulse", rerender = rerender) } shinyApp(ui, server) ```
gadenbuie commented 5 months ago

Duplicate of #1373? Answer: no.