11import asyncio
2+ import atexit
23import contextlib
34import copy
45import logging
@@ -87,7 +88,7 @@ async def _try_server(base_url: str):
8788 _logger .debug (f"Heartbeat { heartbeat_url } returned { response = } " )
8889 if response .status_code == 200 :
8990 return True
90- except (httpx .ConnectError , httpx .ConnectTimeout ):
91+ except (httpx .ConnectError , httpx .ConnectTimeout ): # pragma: nocover
9192 pass
9293 return False
9394
@@ -100,7 +101,7 @@ async def _wait_for_server(base_url: str, timeout: int = 10):
100101 if await _try_server (base_url ):
101102 return
102103
103- if asyncio .get_event_loop ().time () - start_time > timeout :
104+ if asyncio .get_event_loop ().time () - start_time > timeout : # pragma: nocover
104105 raise TimeoutError (f"Server did not start within { timeout } seconds." )
105106
106107 await asyncio .sleep (0.1 ) # Wait before retrying
@@ -163,8 +164,18 @@ def __new__(cls) -> "_Chroma0ClientManager":
163164 if cls .singleton is None :
164165 cls .singleton = super ().__new__ (cls )
165166 cls .singleton .__clients = {}
167+
168+ atexit .register (cls .singleton ._atexit )
169+
166170 return cls .singleton
167171
172+ def _atexit (self ):
173+ try :
174+ loop = asyncio .get_running_loop ()
175+ except RuntimeError :
176+ loop = asyncio .new_event_loop ()
177+ loop .run_until_complete (self .kill_servers ())
178+
168179 @contextlib .asynccontextmanager
169180 async def get_client (self , configs : Config , need_lock : bool = True ):
170181 project_root = str (expand_path (str (configs .project_root ), True ))
@@ -198,10 +209,10 @@ async def get_client(self, configs: Config, need_lock: bool = True):
198209 _logger .debug (f"Unlocking { db_log_path } " )
199210 await lock .release ()
200211
201- def get_processes (self ) -> list [Process ]:
212+ def get_processes (self ) -> list [Process ]: # pragma: nocover
202213 return [i .process for i in self .__clients .values () if i .process is not None ]
203214
204- async def kill_servers (self ):
215+ async def kill_servers (self ): # pragma: nocover
205216 termination_tasks : list [asyncio .Task ] = []
206217 for p in self .get_processes ():
207218 _logger .info (f"Killing bundled chroma server with PID: { p .pid } " )
0 commit comments