33from __future__ import annotations
44
55from collections .abc import Awaitable
6+ from inspect import isawaitable
67from queue import Queue
78from threading import Event , Thread
89from typing import Any , Callable
@@ -26,6 +27,7 @@ def __init__(self, **kwargs):
2627 self .is_pydev_daemon_thread = True
2728 self ._tasks : Queue [tuple [str , Callable [[], Awaitable [Any ]]] | None ] = Queue ()
2829 self ._result : Queue [Any ] = Queue ()
30+ self ._teardown_callbacks : list [Callable [[], Any ] | Callable [[], Awaitable [Any ]]] = []
2931 self ._exception : Exception | None = None
3032
3133 @property
@@ -47,6 +49,9 @@ def run_sync(self, func: Callable[..., Any]) -> Any:
4749 self ._tasks .put (("run_sync" , func ))
4850 return self ._result .get ()
4951
52+ def add_teardown_callback (self , func : Callable [[], Any ] | Callable [[], Awaitable [Any ]]) -> None :
53+ self ._teardown_callbacks .append (func )
54+
5055 def run (self ) -> None :
5156 """Run the thread."""
5257 try :
@@ -55,24 +60,37 @@ def run(self) -> None:
5560 self ._exception = exc
5661
5762 async def _main (self ) -> None :
58- async with create_task_group () as tg :
59- self ._task_group = tg
60- self .started .set ()
61- while True :
62- task = await to_thread .run_sync (self ._tasks .get )
63- if task is None :
64- break
65- func , arg = task
66- if func == "start_soon" :
67- tg .start_soon (arg )
68- elif func == "run_async" :
69- res = await arg
70- self ._result .put (res )
71- else : # func == "run_sync"
72- res = arg ()
73- self ._result .put (res )
74-
75- tg .cancel_scope .cancel ()
63+ try :
64+ async with create_task_group () as tg :
65+ self ._task_group = tg
66+ self .started .set ()
67+ while True :
68+ task = await to_thread .run_sync (self ._tasks .get )
69+ if task is None :
70+ break
71+ func , arg = task
72+ if func == "start_soon" :
73+ tg .start_soon (arg )
74+ elif func == "run_async" :
75+ res = await arg
76+ self ._result .put (res )
77+ else : # func == "run_sync"
78+ res = arg ()
79+ self ._result .put (res )
80+
81+ tg .cancel_scope .cancel ()
82+ finally :
83+ exception = None
84+ for teardown_callback in self ._teardown_callbacks [::- 1 ]:
85+ try :
86+ res = teardown_callback ()
87+ if isawaitable (res ):
88+ await res
89+ except Exception as exc :
90+ if exception is None :
91+ exception = exc
92+ if exception is not None :
93+ raise exception
7694
7795 def stop (self ) -> None :
7896 """Stop the thread.
0 commit comments