From 157fa86fb79a76b7fe2dd1f294d688652e9198cf Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Fri, 14 Aug 2026 23:56:33 +0200 Subject: [PATCH] lib.App: Release async runner in close() close() closes the application's own event loop, but the AsyncRunner is only released in the finally block of run(). An application that creates a runner through call_async() and then calls close(), for instance through the async context manager, therefore leaks the runner, and close() does not fulfill its contract of releasing all resources. Move the AsyncRunner cleanup from the finally block of run() into close() and reset the own-loop flag when the loop is closed, so that close() releases everything and run() only has to call it. Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2 Signed-off-by: Jan Lindemann --- src/python/jw/pkg/lib/App.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/python/jw/pkg/lib/App.py b/src/python/jw/pkg/lib/App.py index a298c1cf..6eb45581 100644 --- a/src/python/jw/pkg/lib/App.py +++ b/src/python/jw/pkg/lib/App.py @@ -216,11 +216,16 @@ class App: # export ) def close(self) -> None: + """Close the application and release all resources""" + if self.__async_runner is not None: + self.__async_runner.close() + self.__async_runner = None if self.__own_eloop: if self.__eloop is not None: if not self.__eloop.is_closed(): self.__eloop.close() self.__eloop = None + self.__own_eloop = False async def __aenter__(self) -> App: return self @@ -344,9 +349,6 @@ class App: # export try: ret = self.eloop.run_until_complete(self.__run(argv)) finally: - if self.__async_runner: - self.__async_runner.close() - self.__async_runner = None self.close() return ret