Skip to content

Commit 023c8dd

Browse files
sjmonsonmarkurtz
andauthored
Fix "Event loop Closed" error (#188)
By default httpx will keep the last few connections open in a pool for re-use. Since we create the httpx connection inside an asyncio task, it is bound to the lifetime of the task rather than the httpx client. Thus when the client attempts to reuse the connection in a new task it fails. There may be performance implications for this change, but since the default is only to re-use the last 5 connections, its likely minor. ([ref](encode/httpx#2959)) Fixes: #147 --------- Co-authored-by: Mark Kurtz <mark.j.kurtz@gmail.com>
1 parent fd1bf3c commit 023c8dd

File tree

3 files changed

+23
-1
lines changed

3 files changed

+23
-1
lines changed

src/guidellm/backend/backend.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ def info(self) -> dict[str, Any]:
110110
"""
111111
...
112112

113+
@abstractmethod
114+
async def reset(self) -> None:
115+
"""
116+
Reset the connection object. This is useful for backends that
117+
reuse connections or have state that needs to be cleared.
118+
"""
119+
...
120+
113121
async def validate(self):
114122
"""
115123
Handle final setup and validate the backend is ready for use.
@@ -126,6 +134,8 @@ async def validate(self):
126134
): # type: ignore[attr-defined]
127135
pass
128136

137+
await self.reset()
138+
129139
@abstractmethod
130140
async def check_setup(self):
131141
"""

src/guidellm/backend/openai.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ def info(self) -> dict[str, Any]:
157157
"chat_completions_path": CHAT_COMPLETIONS_PATH,
158158
}
159159

160+
async def reset(self) -> None:
161+
"""
162+
Reset the connection object. This is useful for backends that
163+
reuse connections or have state that needs to be cleared.
164+
For this backend, it closes the async client if it exists.
165+
"""
166+
if self._async_client is not None:
167+
await self._async_client.aclose()
168+
160169
async def check_setup(self):
161170
"""
162171
Check if the backend is setup correctly and can be used for requests.
@@ -361,7 +370,7 @@ def _get_async_client(self) -> httpx.AsyncClient:
361370
362371
:return: The async HTTP client.
363372
"""
364-
if self._async_client is None:
373+
if self._async_client is None or self._async_client.is_closed:
365374
client = httpx.AsyncClient(
366375
http2=self.http2,
367376
timeout=self.timeout,

tests/unit/mock_backend.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def model(self) -> Optional[str]:
4141
def info(self) -> dict[str, Any]:
4242
return {}
4343

44+
async def reset(self) -> None:
45+
pass
46+
4447
async def prepare_multiprocessing(self):
4548
pass
4649

0 commit comments

Comments
 (0)