Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure onload callbacks scheduled during or after load are still executed #6005

Merged
merged 2 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions panel/io/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ def _eval_panel(
""")
)

doc.on_event('document_ready', partial(state._schedule_on_load, doc))

# Set up instrumentation for logging sessions
logger.info(LOG_SESSION_LAUNCHING, id(doc))
def _log_session_destroyed(session_context):
Expand Down Expand Up @@ -771,6 +773,8 @@ def modify_document(self, doc: 'Document'):

logger.info(LOG_SESSION_LAUNCHING, id(doc))

doc.on_event('document_ready', partial(state._schedule_on_load, doc))

if config.autoreload:
path = self._runner.path
argv = self._runner._argv
Expand Down
20 changes: 9 additions & 11 deletions panel/io/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,22 +376,24 @@ def _schedule_on_load(self, doc: Document, event) -> None:

def _on_load(self, doc: Optional[Document] = None) -> None:
doc = doc or self.curdoc
callbacks = self._onload.pop(doc, [])
if not callbacks:
if doc not in self._onload:
self._loaded[doc] = True
return

from ..config import config
from .profile import profile_ctx
with set_curdoc(doc):
if (doc and doc in self._launching) or not config.profiler:
for cb, threaded in callbacks:
self.execute(cb, schedule='thread' if threaded else False)
while doc in self._onload:
for cb, threaded in self._onload.pop(doc):
self.execute(cb, schedule='thread' if threaded else False)
self._loaded[doc] = True
return

with profile_ctx(config.profiler) as sessions:
for cb, threaded in callbacks:
self.execute(cb, schedule='thread' if threaded else False)
while doc in self._onload:
for cb, threaded in self._onload.pop(doc):
self.execute(cb, schedule='thread' if threaded else False)
path = doc.session_context.request.path
self._profiles[(path+':on_load', config.profiler)] += sessions
self.param.trigger('_profiles')
Expand Down Expand Up @@ -680,7 +682,7 @@ def onload(self, callback: Callable[[], None | Awaitable[None]] | Coroutine[Any,
threaded: bool
Whether the onload callback can be threaded
"""
if self.curdoc is None or self._is_pyodide:
if self.curdoc is None or self._is_pyodide or self.loaded:
if self._thread_pool:
future = self._thread_pool.submit(partial(self.execute, callback, schedule=False))
future.add_done_callback(self._handle_future_exception)
Expand All @@ -689,10 +691,6 @@ def onload(self, callback: Callable[[], None | Awaitable[None]] | Coroutine[Any,
return
elif self.curdoc not in self._onload:
self._onload[self.curdoc] = []
try:
self.curdoc.on_event('document_ready', partial(self._schedule_on_load, self.curdoc))
except AttributeError:
pass # Document already cleaned up
self._onload[self.curdoc].append((callback, threaded))

def on_session_created(self, callback: Callable[[BokehSessionContext], None]) -> None:
Expand Down
50 changes: 50 additions & 0 deletions panel/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,56 @@ def test_serve_can_serve_bokeh_app_from_file():
assert "/bk-app" in server._tornado.applications


def test_server_on_load_after_init(threads, port):
loaded = []

def cb():
loaded.append(state.loaded)

def cb2():
state.execute(cb, schedule=True)

def app():
state.onload(cb)
state.onload(cb2)
# Simulate rendering
def loaded():
state.curdoc
state._schedule_on_load(state.curdoc, None)
state.execute(loaded, schedule=True)
return 'App'

serve_and_request(app)

# Checks whether onload callback was executed twice once before and once after load
wait_until(lambda: loaded == [False, True])


def test_server_on_load_during_load(threads, port):
loaded = []

def cb():
loaded.append(state.loaded)

def cb2():
state.onload(cb)

def app():
state.onload(cb)
state.onload(cb2)
# Simulate rendering
def loaded():
state.curdoc
state._schedule_on_load(state.curdoc, None)
state.execute(loaded, schedule=True)
return 'App'

serve_and_request(app)

# Checks whether onload callback was executed twice once before and once during load
wait_until(lambda: loaded == [False, False])


def test_server_thread_pool_on_load(threads, port):
counts = []

Expand Down
Loading