Skip to content

Commit 69a23d5

Browse files
committed
Add .on_end() callback registration for before the WS is closed
`reactive.isolate()` before calling callbacks
1 parent 3430b9b commit 69a23d5

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

shiny/express/_stub_session.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ async def close(self, code: int = 1001) -> None:
5858
def _is_hidden(self, name: str) -> bool:
5959
return False
6060

61+
def on_end(
62+
self,
63+
fn: Callable[[], None] | Callable[[], Awaitable[None]],
64+
) -> Callable[[], None]:
65+
return lambda: None
66+
6167
def on_ended(
6268
self,
6369
fn: Callable[[], None] | Callable[[], Awaitable[None]],

shiny/session/_session.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,25 @@ async def close(self, code: int = 1001) -> None:
211211
@abstractmethod
212212
def _is_hidden(self, name: str) -> bool: ...
213213

214+
@abstractmethod
215+
def on_end(
216+
self,
217+
fn: Callable[[], None] | Callable[[], Awaitable[None]],
218+
) -> Callable[[], None]:
219+
"""
220+
Registers a function to be called hopefully before the client is disconnected.
221+
222+
Parameters
223+
----------
224+
fn
225+
The function to call.
226+
227+
Returns
228+
-------
229+
:
230+
A function that can be used to cancel the registration.
231+
"""
232+
214233
@add_example()
215234
@abstractmethod
216235
def on_ended(
@@ -559,7 +578,9 @@ def __init__(
559578
self._outbound_message_queues = OutBoundMessageQueues()
560579

561580
self._file_upload_manager: FileUploadManager = FileUploadManager()
581+
self._on_end_callbacks = _utils.AsyncCallbacks()
562582
self._on_ended_callbacks = _utils.AsyncCallbacks()
583+
self._has_run_session_end_tasks: bool = False
563584
self._has_run_session_ended_tasks: bool = False
564585
self._downloads: dict[str, DownloadInfo] = {}
565586
self._dynamic_routes: dict[str, DynamicRouteHandler] = {}
@@ -576,20 +597,37 @@ def _register_session_ended_callbacks(self) -> None:
576597
# Clear file upload directories, if present
577598
self.on_ended(self._file_upload_manager.rm_upload_dir)
578599

600+
async def _run_session_end_tasks(self) -> None:
601+
if self._has_run_session_end_tasks:
602+
return
603+
self._has_run_session_end_tasks = True
604+
605+
try:
606+
with isolate():
607+
await self._on_end_callbacks.invoke()
608+
except Exception as e:
609+
warnings.warn(
610+
f"Error running session end tasks: {str(e)}",
611+
SessionWarning,
612+
stacklevel=2,
613+
)
614+
579615
async def _run_session_ended_tasks(self) -> None:
580616
if self._has_run_session_ended_tasks:
581617
return
582618
self._has_run_session_ended_tasks = True
583619

584620
try:
585-
await self._on_ended_callbacks.invoke()
621+
with isolate():
622+
await self._on_ended_callbacks.invoke()
586623
finally:
587624
self.app._remove_session(self)
588625

589626
def is_stub_session(self) -> Literal[False]:
590627
return False
591628

592629
async def close(self, code: int = 1001) -> None:
630+
await self._run_session_end_tasks()
593631
await self._conn.close(code, None)
594632
await self._run_session_ended_tasks()
595633

@@ -713,6 +751,7 @@ def verify_state(expected_state: ConnectionState) -> None:
713751
finally:
714752
await self.close()
715753
finally:
754+
await self._run_session_end_tasks()
716755
await self._run_session_ended_tasks()
717756

718757
def _manage_inputs(self, data: dict[str, object]) -> None:
@@ -1079,8 +1118,15 @@ def _decrement_busy_count(self) -> None:
10791118
self._send_message_sync({"busy": "idle"})
10801119

10811120
# ==========================================================================
1082-
# On session ended
1121+
# On session end / ended
10831122
# ==========================================================================
1123+
1124+
def on_end(
1125+
self,
1126+
fn: Callable[[], None] | Callable[[], Awaitable[None]],
1127+
) -> Callable[[], None]:
1128+
return self._on_end_callbacks.register(wrap_async(fn))
1129+
10841130
def on_ended(
10851131
self,
10861132
fn: Callable[[], None] | Callable[[], Awaitable[None]],
@@ -1221,6 +1267,12 @@ def __init__(self, root_session: Session, ns: ResolvedId) -> None:
12211267
def _is_hidden(self, name: str) -> bool:
12221268
return self._root_session._is_hidden(name)
12231269

1270+
def on_end(
1271+
self,
1272+
fn: Callable[[], None] | Callable[[], Awaitable[None]],
1273+
) -> Callable[[], None]:
1274+
return self._root_session.on_end(fn)
1275+
12241276
def on_ended(
12251277
self,
12261278
fn: Callable[[], None] | Callable[[], Awaitable[None]],

0 commit comments

Comments
 (0)