@@ -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