Skip to content

Commit

Permalink
Ensure onload callbacks scheduled during or after load are still exec…
Browse files Browse the repository at this point in the history
…uted (#6005)
  • Loading branch information
philippjfr committed Dec 7, 2023
1 parent ae9cee8 commit 7bc8c83
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 11 deletions.
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

0 comments on commit 7bc8c83

Please sign in to comment.